Problem

 monolithic application

 no reusability

 slow tests

 bigger application, more mess
Possible solutions

 extract common functionality to modules

 create gems

 tie gem with rails application with railtie

 rails engine
What is a Gem ?

 packaged application or library
      code
      tests
      documentation
      gemspec
Creating gem

 bundle gem simplify                ├── Gemfile
                                     ├── LICENSE.txt
                                     ├── README.md
 structure                          ├── Rakefile
      code (lib/)                   ├── lib
                                     │ ├── simplify
      tests (test/ or spec/)        │ │ └── version.rb
      documentation (doc/ README)   │ └── simplify.rb
                                     └── simplify.gemspec
      executable files (bin/)

 information about gem
    simplify.gemspec
Releasing Gem

     change version

     run `bundle`

     commit changes

     run `rake release`

$ rake release
stupid_spam_protection 0.0.2 built to pkg/stupid_spam_protection-0.0.2.gem
Tagged v0.0.2
Pushed git commits and tags
Pushed stupid_spam_protection 0.0.2 to rubygems.org
in-memory testing with sqlite
spec/spec_helper.rb
require "active_record"
require "sqlite3”

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

load "db/schema.rb"                      db/schema.rb
                                         ActiveRecord::Schema.define do
RSpec.configure do |config|               create_table "items", :force => true do |t|
 config.around do |example|                t.string "name”
  ActiveRecord::Base.transaction do        t.datetime "created_at", :null => false
   example.run                             t.datetime "updated_at", :null => false
   raise ActiveRecord::Rollback           end
  end
 end
end
testing with mysql
                       database
spec/spec_helper.rb

 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                                   config = HashWithIndifferentAccess.new({
                                         :adapter => "mysql2",
 load "db/schema.rb"                     :host => "127.0.0.1",
                                         :username => "root",
 RSpec.configure do |config|             :password => "",
  # the same from previous slide         :database => ”database_name”
 end                                   })
Tie gem to Rails app
           Rails::Railtie
 extend Rails framework
    load all needed dependencies

 several hooks methods for all needs

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

module WnmSupport                                  lib/wnm_support/railtie.rb
 class Railtie < Rails::Railtie
  initializer "wnm_support.view_helpers" do
    ActionView::Base.send :include,
BoolToHuman
  end
 end
end
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
    end
  end
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
      generators
   config.to_prepare do
    AppController.send(:include,
   Controller::Authentication)
   end
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
Engine walkthrough

 generate new engine

 basic commands

 testing

 mounting to host app

 overriding defaults
Generating Engine

 rails plugin new blorgh --mountable –
  full

 everything (assets, controller, models,
  views) are inside Blorgh namespace
    module Blorgh
     class Engine < ::Rails::Engine
      isolate_namespace Blorgh
     end
    end
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
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}" }      RSpec.configure do |config|
  password "too_secret"                       config.include Blorgh ::Engine.routes.url_helper
  password_confirmation { password }         end
 end
end
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"
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 %>
Overriding engine classes

       Controller, Model, etc.
            reopening classes
                 class_eval, module_eval

module MySite
 class Railtie < ::Rails::Railtie
  config.to_prepare do
    Dir["app/decorators/**/*.rb"].each do |path|
     load path                                     app/decorators/models/blorgh/article.rb
    end
  end                                              Blorgh::Article.class_eval do
 end                                                has_many :users, :class_name => „User'
end                                                end
Overriding engine views

 simple copy and paste view file that you want to
  override to the same location in host app
Application Modules with
        Rails Engine
 every new engine means new namespace

 application modules means for us more engines with
  same namespace

 inspiration from refinerycms
The same namespace
       different engines
 module Wnm                         module Wnm
  module Core                        module Pages
   class Engine < ::Rails::Engine     class Engine < ::Rails::Engine
    isolate_namespace Wnm              isolate_namespace Wnm
    engine_name :wnm_core              engine_name :wnm_pages
  end                                end
 end                                end



rake wnm_core:install:migrations     rake wnm_pages:install:migrations

Wnm::Core::Engine.load_seed          Wnm::Pages::Engine.load_seed



                 both are nested in `Wnm` namespace
Developing from local
           Production from git
   bundler does not allow to have same package from
    different sources in Gemfile

export LOAD_GEMS_FROM_LOCAL=1               .rvmrc

if ENV["LOAD_GEMS_FROM_LOCAL"] == "1"                             Gemfile
  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
Deploying

 change LOAD_GEMS_FROM_LOCAL to 0 and run
  `bundle`

 commit and push

 `cap deploy`
Tips

 if you are overriding views heavily always prefix routes
  path with engine module name
<%= link_to page.name, wnm_page.page_path(page), :title => page.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
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
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
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
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

Rails Engine | Modular application

  • 2.
    Problem  monolithic application no reusability  slow tests  bigger application, more mess
  • 3.
    Possible solutions  extractcommon functionality to modules  create gems  tie gem with rails application with railtie  rails engine
  • 4.
    What is aGem ?  packaged application or library  code  tests  documentation  gemspec
  • 5.
    Creating gem  bundlegem simplify ├── Gemfile ├── LICENSE.txt ├── README.md  structure ├── Rakefile  code (lib/) ├── lib │ ├── simplify  tests (test/ or spec/) │ │ └── version.rb  documentation (doc/ README) │ └── simplify.rb └── simplify.gemspec  executable files (bin/)  information about gem  simplify.gemspec
  • 6.
    Releasing Gem  change version  run `bundle`  commit changes  run `rake release` $ rake release stupid_spam_protection 0.0.2 built to pkg/stupid_spam_protection-0.0.2.gem Tagged v0.0.2 Pushed git commits and tags Pushed stupid_spam_protection 0.0.2 to rubygems.org
  • 7.
    in-memory testing withsqlite spec/spec_helper.rb require "active_record" require "sqlite3” ActiveRecord::Base.establish_connectio n{ :adapter => "sqlite3”, :database => ":memory:” } load "db/schema.rb" db/schema.rb ActiveRecord::Schema.define do RSpec.configure do |config| create_table "items", :force => true do |t| config.around do |example| t.string "name” ActiveRecord::Base.transaction do t.datetime "created_at", :null => false example.run t.datetime "updated_at", :null => false raise ActiveRecord::Rollback end end end end
  • 8.
    testing with mysql database spec/spec_helper.rb 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 config = HashWithIndifferentAccess.new({ :adapter => "mysql2", load "db/schema.rb" :host => "127.0.0.1", :username => "root", RSpec.configure do |config| :password => "", # the same from previous slide :database => ”database_name” end })
  • 9.
    Tie gem toRails app Rails::Railtie  extend Rails framework  load all needed dependencies  several hooks methods for all needs require "wnm_support/railtie" if defined?(Rails) lib/wnm_support.rb module WnmSupport lib/wnm_support/railtie.rb class Railtie < Rails::Railtie initializer "wnm_support.view_helpers" do ActionView::Base.send :include, BoolToHuman end end end
  • 10.
    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 end end
  • 11.
    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  generators config.to_prepare do AppController.send(:include, Controller::Authentication) end
  • 12.
    What is RailsEngine ?  every rails app is an engine  Rails::Engine < Rails::Railtie  all together  gem skeleton  railtie  structure for app  dummy app for testing  assets
  • 13.
    Engine walkthrough  generatenew engine  basic commands  testing  mounting to host app  overriding defaults
  • 14.
    Generating Engine  railsplugin new blorgh --mountable – full  everything (assets, controller, models, views) are inside Blorgh namespace module Blorgh class Engine < ::Rails::Engine isolate_namespace Blorgh end end
  • 15.
    Basics commands  inroot 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
  • 16.
    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}" } RSpec.configure do |config| password "too_secret" config.include Blorgh ::Engine.routes.url_helper password_confirmation { password } end end end
  • 17.
    Mounting Engine tohost 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"
  • 18.
    Get to engineand 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 %>
  • 19.
    Overriding engine classes  Controller, Model, etc.  reopening classes  class_eval, module_eval module MySite class Railtie < ::Rails::Railtie config.to_prepare do Dir["app/decorators/**/*.rb"].each do |path| load path app/decorators/models/blorgh/article.rb end end Blorgh::Article.class_eval do end has_many :users, :class_name => „User' end end
  • 20.
    Overriding engine views simple copy and paste view file that you want to override to the same location in host app
  • 21.
    Application Modules with Rails Engine  every new engine means new namespace  application modules means for us more engines with same namespace  inspiration from refinerycms
  • 22.
    The same namespace different engines module Wnm module Wnm module Core module Pages class Engine < ::Rails::Engine class Engine < ::Rails::Engine isolate_namespace Wnm isolate_namespace Wnm engine_name :wnm_core engine_name :wnm_pages end end end end rake wnm_core:install:migrations rake wnm_pages:install:migrations Wnm::Core::Engine.load_seed Wnm::Pages::Engine.load_seed both are nested in `Wnm` namespace
  • 23.
    Developing from local Production from git  bundler does not allow to have same package from different sources in Gemfile export LOAD_GEMS_FROM_LOCAL=1 .rvmrc if ENV["LOAD_GEMS_FROM_LOCAL"] == "1" Gemfile 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
  • 24.
    Deploying  change LOAD_GEMS_FROM_LOCALto 0 and run `bundle`  commit and push  `cap deploy`
  • 25.
    Tips  if youare overriding views heavily always prefix routes path with engine module name <%= link_to page.name, wnm_page.page_path(page), :title => page.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
  • 26.
    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
  • 27.
    Problems  if youare 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
  • 28.
    Why we didthis 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
  • 29.
    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