rails engines in large apps
DESCRIPTION
TRANSCRIPT
Rails engines inlarge apps
Created by / Enrico Teotti @agenteo
Start off with a smallapplication
and the app complexity willgrow over time
Start off with a big complexapplication
ignoring complexity will hurtyou
In Ruby on Rails that is:
too many responsibilities inactive record models
long controller methods
helpers modules doing agazillion things
concerns
These poor decisions are sometime justified as:
"This is the Rails way!""Those are the Rails
conventions!""This is what a Rails developer
expect to see!"
bullshit
Rails gives conventions that fit small application domains but doesn'thave any for larger problems
How can Ruby on Rails engineshelp?
Engines can drop in functionality in your Rails monolithic app
DeviseKaminari
But they can do more that that"Rails::Engine allows you to wrap a specific Rails application or subset of
functionality and share it with other applications or within a largerpackaged application. Since Rails 3.0, every Rails::Application is just an
engine, which allows for simple feature and application sharing."
Rails API
we will create very specific engine only used in this app
Domain ModelThis is a domain model taken from Evan's book DDD
It is a simplified version of a real live problem
use Engines as building bricksof the application!
First, create an integration testin the main appspec/features/ship_cargo_spec.rb
require 'spec_helper'
feature 'As a staff member I want to ship a cargo' do scenario %{ Given I am on the cargo shipping page When I fill in a customer And I fill in a origin and destination Then I want to see an itinerary } do visit '/ship_cargo' endend
rspecF
Failures:
1) As a staff member I want to ship a cargoGiven I am on the cargo shipping pageWhen I fill in a customerAnd I fill in a origin and destinationThen I want to see an itineraryFailure/Error: visit '/ship_cargo'ActionController::RoutingError:No route matches [GET] "/ship_cargo"# ./spec/features/ship_cargo_spec.rb:10:in ̀block (2 levels) in <top (required)="">'
Finished in 0.00523 seconds1 example, 1 failure </top>
Create an enginerails plugin new cargo_shipping --mountable -T --dummy-path=spec/dummy
You can create a nested folderstructure
If you want to be more formal about the separation ofresponsibilities between the engines
The engine name will match ubiquitous language you share withstakeholders
engines/├── application_layer├── domain_layer│ ├── billing│ ├── customer│ └── shipping└── presentation_layer └── cargo_shipping
Main app Gemfilegem 'cargo_shipping', path: 'engines/presentation_layer/cargo_shipping'
Cargo Shipping Enginestructure
engines/presentation_layer/cargo_shipping/├── Gemfile├── Gemfile.lock├── MIT-LICENSE├── README.rdoc├── Rakefile├── app│ ├── assets│ ├── controllers│ ├── helpers│ ├── mailers│ └── views├── cargo_shipping.gemspec├── config│ └── routes.rb├── lib│ ├── cargo_shipping│ ├── cargo_shipping.rb│ └── tasks└── spec └── dummy
CargoShipping Engine routesengines/presentation_layer/cargo_shipping/config/routes.rbmodule PresentationLayer CargoShipping::Engine.routes.draw do get 'ship_cargo', to: 'ship_cargo#new' endend
CargoShipping Enginecontroller
engines/presentation_layer/cargo_shipping/app/controllers/cargo_shipping/ship_cargo_controller.rb
module CargoShipping class ShipCargoController < ActionController::Base
def new end
endend
Run the test again!F
Failures:
1) As a staff member I want to ship a cargoGiven I am on the cargo shipping pageWhen I fill in a customerAnd I fill in a origin and destinationThen I want to see an itineraryFailure/Error: visit '/ship_cargo'ActionController::RoutingError:No route matches [GET] "/ship_cargo"# ./spec/features/ship_cargo_spec.rb:10:in ̀block (2 levels) in <top (required)="">'
Finished in 0.00523 seconds1 example, 1 failure </top>
We need to change the mainapp routes!
config/routes.rbMaersk::Application.routes.draw do mount CargoShipping::Engine, at: "/"end
rspec.
Finished in 0.05205 seconds1 example, 0 failures
CargoShipping Engineengines/presentation_layer/cargo_shipping/app/controllers/cargo_shipping/ship_cargo_controller.rb
module CargoShipping class ShipCargoController < ActionController::Base
def new @customers = Customers::CustomerRepository.all end
endend
rspecF
Failures:
1) As a staff member I want to ship a cargoGiven I am on the cargo shipping pageWhen I fill in a customerAnd I fill in a origin and destinationThen I want to see an itineraryFailure/Error: visit '/ship_cargo'NameError:uninitialized constant CargoShipping::ShipCargoController::Customers# ./engines/presentation_layer/cargo_shipping/app/controllers/cargo_shipping/ship_cargo_controller.rb:5:in ̀new'# ./spec/features/ship_cargo_spec.rb:10:in ̀block (2 levels) in <top (required)="">'
Finished in 0.04333 seconds1 example, 1 failure </top>
Create a domain layer enginerails plugin new customers --mountable -T --dummy-path=spec/dummy
Main app Gemfilegem 'cargo_shipping', path: 'engines/presentation_layer/cargo_shipping'gem 'customers', path: 'engines/domain_layer/customers'
Generating models inCustomers Engine
engines/domain_layer/customers/app/models/customers/customer_repository.rb
rails generate model CustomerRepository email nameinvoke active_recordcreate db/migrate/20130921154844_create_customers_customer_repositories.rbcreate app/models/customers/customer_repository.rbinvoke rspeccreate spec/models/customers/customer_repository_spec.rb
rspec.
Finished in 0.05205 seconds1 example, 0 failures
Mailing list:https://groups.google.com/forum/?hl=en#!forum/components-in-rails
References:Wrangling Large Rails CodebasesArchitecting your Rails app for success!http://pivotallabs.com/leave-your-migrations-in-your-rails-engines/http://pivotallabs.com/experience-report-engine-usage-that-didn-t-work/
THE ENDEnrico Teotti / @agenteo / teotti.com