rails engine | modular application

29
Rails Engine Miroslav Hetteš @mirrec7 [email protected]

Post on 20-Oct-2014

5.402 views

Category:

Technology


3 download

DESCRIPTION

presents how to make modular application using ruby engines

TRANSCRIPT

Page 1: Rails Engine | Modular application

Rails Engine

Miroslav Hetteš

@mirrec7

[email protected]

Page 2: Rails Engine | Modular application

Problem

monolithic application

no reusability

slow tests

bigger application, more mess

Page 3: Rails Engine | Modular application

Possible solutions

extract common functionality to modules

create gems

tie gem with rails application with railtie

rails engine

Page 4: Rails Engine | Modular application

What is a Gem ?

packaged application or library code tests documentation gemspec

Page 5: Rails Engine | Modular application

Creating gem

bundle gem simplify

structure code (lib/) tests (test/ or spec/) documentation (doc/ README) executable files (bin/)

information about gem simplify.gemspec

├── Gemfile├── LICENSE.txt├── README.md├── Rakefile├── lib│   ├── simplify│   │   └── version.rb│   └── simplify.rb└── simplify.gemspec

Page 6: Rails Engine | Modular application

Releasing Gem

change version

run `bundle`

commit changes

run `rake release`

$ rake releasestupid_spam_protection 0.0.2 built to pkg/stupid_spam_protection-0.0.2.gemTagged v0.0.2Pushed git commits and tagsPushed stupid_spam_protection 0.0.2 to rubygems.org

Page 7: Rails Engine | Modular application

in-memory testing with sqlite

require "active_record"require "sqlite3”

ActiveRecord::Base.establish_connection { :adapter => "sqlite3”, :database => ":memory:”}

load "db/schema.rb"

RSpec.configure do |config| config.around do |example| ActiveRecord::Base.transaction do example.run raise ActiveRecord::Rollback end endend

ActiveRecord::Schema.define do create_table "items", :force => true do |t| t.string "name” t.datetime "created_at", :null => false t.datetime "updated_at", :null => false end

spec/spec_helper.rb

db/schema.rb

Page 8: Rails Engine | Modular application

testing with mysql database

begin ActiveRecord::Base.establish_connection(config)rescue ActiveRecord::Base.establish_connection(config.merge('database' => nil)) ActiveRecord::Base.connection.create_database(config['database'], { :charset => 'utf8’, :collation => 'utf8_unicode_ci’})end

load "db/schema.rb"

RSpec.configure do |config| # the same from previous slideend

config = HashWithIndifferentAccess.new({ :adapter => "mysql2", :host => "127.0.0.1", :username => "root", :password => "", :database => ”database_name”})

spec/spec_helper.rb

Page 9: Rails Engine | Modular application

Tie gem to Rails app Rails::Railtie

extend Rails framework load all needed dependencies

several hooks methods for all needs

module WnmSupport class Railtie < Rails::Railtie initializer "wnm_support.view_helpers" do ActionView::Base.send :include, BoolToHuman end endend

require "wnm_support/railtie" if defined?(Rails) lib/wnm_support.rb

lib/wnm_support/railtie.rb

Page 10: Rails Engine | Modular application

Railtie hooks 1

Plugin hooks initializer generators rake_tasks console

initializer "wnm_support.active_record_ext" do ActiveSupport.on_load(:active_record) do ActiveRecord::Base.send :include, WnmSupport::ActiveRecordExt::DestroyValidation endend

Page 11: Rails Engine | Modular application

Railtie hooks 2

Rails configuration hooks to_prepare (once in production, every request in

development)

before_initialize after_initialize app_middleware before_configuration before_eager_load generatorsconfig.to_prepare do AppController.send(:include, Controller::Authentication)end

Page 12: Rails Engine | Modular application

What is Rails Engine ?

every rails app is an engine

Rails::Engine < Rails::Railtie

all together gem skeleton railtie structure for app dummy app for testing assets

Page 13: Rails Engine | Modular application

Engine walkthrough

generate new engine

basic commands

testing

mounting to host app

overriding defaults

Page 14: Rails Engine | Modular application

Generating Engine

rails plugin new blorgh --mountable –full

everything (assets, controller, models, views) are inside Blorgh namespacemodule Blorgh class Engine < ::Rails::Engine isolate_namespace Blorgh endend

Page 15: Rails Engine | Modular application

Basics commands

in root directory of the engine bundle rake rspec rails generate # generate code for engine

in spec/dummy or test/dummy location rails console rails server rails generate # generate code for dummy app

Page 16: Rails Engine | Modular application

Testing

generally as in standard rails application

engine is tested inside dummy app

small problems with factory girl explicite model class in factory girl include routes for request tests

FactoryGirl.define do factory :user, :class => Blorgh ::User do sequence(:login) { |n| "admin_#{n}" } password "too_secret" password_confirmation { password } endend

RSpec.configure do |config| config.include Blorgh ::Engine.routes.url_helpersend

Page 17: Rails Engine | Modular application

Mounting Engine to host application

Gemfile gem “blorgh”, :path => “~/Sites/blorgh”

install migration `rake blorgh:install:migrations`

load seeds data Blorgh::Engine.load_seed # in rails console

require assets if needed *= require blorgh/application #

assets/stylesheets/application.css

mount to routes.rb mount Blorgh::Engine => "/blorgh"

Page 18: Rails Engine | Modular application

Get to engine and get back

routes to engine from host app prefix with engine name example engine “blorgh”

<%= link_to “Users”, blorgh.users_path %>

routes from engine to host app prefix with main_app example engine “blorgh”

<%= link_to “Home page”,main_app.root_path %>

Page 19: Rails Engine | Modular application

Controller, Model, etc. reopening classes

class_eval, module_eval

Overriding engine classes

Blorgh::Article.class_eval do has_many :users, :class_name => ‘User'end

app/decorators/models/blorgh/article.rb

module MySite class Railtie < ::Rails::Railtie config.to_prepare do Dir["app/decorators/**/*.rb"].each do |path| load path end end endend

Page 20: Rails Engine | Modular application

Overriding engine views

simple copy and paste view file that you want to override to the same location in host app

Page 21: Rails Engine | Modular application

Application Modules with Rails Engine

every new engine means new namespace

application modules means for us more engines with same namespace

inspiration from refinerycms

Page 22: Rails Engine | Modular application

The same namespacedifferent engines

module Wnm module Core class Engine < ::Rails::Engine isolate_namespace Wnm engine_name :wnm_core endend

module Wnm module Pages class Engine < ::Rails::Engine isolate_namespace Wnm engine_name :wnm_pages endend

rake wnm_core:install:migrations

Wnm::Core::Engine.load_seed

rake wnm_pages:install:migrations

Wnm::Pages::Engine.load_seed

both are nested in `Wnm` namespace

Page 23: Rails Engine | Modular application

Developing from localProduction from git

bundler does not allow to have same package from different sources in Gemfile

if ENV["LOAD_GEMS_FROM_LOCAL"] == "1" gem "wnm_core", :path => "~/Sites/wnm_core" gem "wnm_pages", :path => "~/Sites/wnm_pages" gem "wnm_customers", :path => "~/Sites/wnm_customers"else gem "wnm_core", :git => "git@…/wnm_core.git", :tag => "v1.0.0" gem "wnm_pages", :git => "git@…/wnm_pages.git", :tag => "v1.0.0" gem "wnm_customers", :git => "git@.../wnm_customers.git", :tag => "v1.0.0"end

export LOAD_GEMS_FROM_LOCAL=1 .rvmrc

Gemfile

Page 24: Rails Engine | Modular application

Deploying

change LOAD_GEMS_FROM_LOCAL to 0 and run `bundle`

commit and push

`cap deploy`

Page 25: Rails Engine | Modular application

Tips

if you are overriding views heavily always prefix routes path with engine module name

use class methods instead of constants

always write full path to class/module if it is in namespace ::ApplicationController instead of ApplicationController

testing is essential

<%= link_to page.name, wnm_page.page_path(page), :title => page.name %>

Page 26: Rails Engine | Modular application

Why use Rails::Engine ?

reusable code code hole application assets

modular thinking

gem with MVC

faster test

testing gem in dummy app infrastructure

some well known engines devise, kaminari, refinerycms

Page 27: Rails Engine | Modular application

Problems

if you are bending engines weird thinks can happen

a lot of weird errors application does not run in development mode from git

sources tests passed, but in browser it is not running in browser it is running but tests are throwing exceptions

overriding classes in host app

helper methods from host app does not work in overridden views

some things from documentation does not work at all

Page 28: Rails Engine | Modular application

Why we did this like that ?

a lot of our project are in general the same

build core for site quickly “scaffold” for application

rails engine is infrastructure for modular application

we can have backend and frontend in the same application module

Page 29: Rails Engine | Modular application

Sources

http://guides.rubygems.org/what-is-a-gem/ http://www.engineyard.com/blog/2010/extending-rails-3-with-

railties/ http://www.slideshare.net/AndyMaleh/rails-engine-patterns http://edgeguides.rubyonrails.org/engines.html http://edgeapi.rubyonrails.org/classes/Rails/Railtie.html http://edgeapi.rubyonrails.org/classes/Rails/Engine.html http://pivotallabs.com/users/shagemann/blog/articles/1994-

migrating-from-a-single-rails-app-to-a-suite-of-rails-engines http://railscasts.com/episodes/301-extracting-a-ruby-gem http://railscasts.com/episodes/303-publishing-a-gem http://railscasts.com/episodes/277-mountable-engines