Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012
1. Rails Engine Patterns
Andy Maleh
Software Engineer for Groupon (Oct 2011 - Oct 2012)
Former Senior Consultant for Obtiva
2. Problem
Difficulty reusing functionality cutting across:
Models
Views
Controllers
Assets (JS, CSS, Images)
Duplication across all web application layers.
4. Solution
Break common behavior into Rails Engines
Customize models/controllers/helpers in each
project where needed by reopening classes
Customize Rails views in each project as needed
by overriding templates
Link to Rails Engines in Gemfile via Git repo
8. What is a Rails Engine?
Ruby Gem
+
MVC stack elements
9. What is a Rails Engine?
Rails Engines let applications reuse:
Models / Views / Controllers / Helpers
Assets (JS, CSS, Images)
Routes
Rake tasks
Generators
10. What is a Rails Engine?
Rails Engines let applications reuse:
Initializers
RSpec / Cucumber
Rack application with middleware stack
Migrations and seed data
Libraries
11. Engine Definition
An engine structure is similar to a Rails app
having app, config, lib, spec, features, etc…
lib/engine_name.rb (engine loader)
lib/engine_name/engine.rb (engine class)
To reuse engine, generate gemspec (using
“Jeweler or Gemcutter”)
14. Isolated Engines
To avoid Ruby namespace clash with
Models/Helpers/Controllers, you can define an
isolated (namespaced) engine.
Add “isolate_namespace MyEngine” to engine
class body
For more details, go to
http://edgeapi.rubyonrails.org/classes/Rails/
Engine.html
17. Load Order
Typically Rails app files load first before Engine
files.
Strongly recommended to reverse so that
engine’s code is customizable in app
18. Load Order
Reversing load order can happen in one of two
ways:
Patching “active_support/dependencies.rb” in Rails
3.1- (see next slide)
Adjusting railties_order in Rails 3.2+
19.
20. Engine Configuration
Engines can be configured to customize rack
middleware, load paths, generators, and Rails
component paths. More details at: http
://edgeapi.rubyonrails.org/classes/Rails/
Engine.html
21. View and Asset
Customization
Engine View and Asset presentation can be
customized by redefining files in Rails App
Customizations completely override files in Engine
Examples of customizable View and Asset files:
ERB/Haml
JS
CSS
Images
22. Ruby Code Customization
Model/Helper/Controller behavior can be
customized be redefining .rb files in Rails App
Customizations get mixed in with files in Engine
This allows:
Adding new methods/behavior
Replacing existing methods
Extending existing methods (alias_method_chain)
27. Recommended Engine
Code Management
• Each Engine lives in own Repo independent of
Rails App
• Each Engine has its own Gem dependencies
(Gemfile)
• Engines preferably share the same Ruby version
as Rails App
28. Typical Development
Process
1.Make changes in engine, rake, and commit
obtaining a new GIT ref
2.Update Gemfile in app with new git ref, run
“bundle install” (getting ride of symlink)
3.Rake and commit changes in app.
4.If more changes in engine are needed go back to
step 1
29. Improved Development
Process
1. Work in app and engine until done WITHOUT running
“bundle install”
2. Rake and commit changes in engine obtaining a new git ref
3. Update Gemfile in app with git ref, run “bundle install”
4. Rake and commit changes in app
31. Engines Reuse Engines
When multiple levels of depth are involved,
commit repos and update Gemfile from the
bottom up
Example: Engine 2 => Engine 1 => App
1. Commit in Engine 2
2. Update Gemfile in Engine 1 and commit
3. Update Gemfile in App and commit
32. Rails Engine Patterns
Goals:
Keep engine code agnostic of app customizations
Prevent bi-directional coupling to simplify
reasoning about code
Avoid app dependent conditionals to improve code
maintainability
33. Pattern - Common Domain
Problem: Multiple Rails Apps need to share a basic
domain model including default CRUD and
presentation behavior
Example: need to reuse address model and form entry
behavior
34. Pattern - Common Domain
Solution:
In engine, include basic domain models (base
behavior, common associations) with their views,
CRUD controllers, and routes.
In each app, define domain model specialized
behavior, extra associations, and custom views
Make sure routes are declared with
MyEngine::Engine.routes.draw (not Rails App name)
35. Pattern - Common Domain
Example: address.rb, addresses_controller.rb,
address route, and _address.html.erb
36. Pattern - Expose Helper
Problem: need to customize presentation logic for a
view in one app only, but keep the same logic in others
Example:
One App needs to hide address1 and county for non-
government users.
Other Apps wants to keep the fields displayed.
You might start by overriding view, but then realize it is
duplicating many elements just to hide a couple fields.
37. Pattern - Expose Helper
Solution:
In Engine, extract helper logic that needs
customization into its own helper.
In App, redefine that new helper with
customizations.
40. Pattern - Expose Helper
Remove custom view from App
Use requires_extended_address? helper in Engine
wherever the App used government_user?
In Engine, define requires_extended_address? to
return true
In App, define requires_extended_address? to return
government_user?
42. Pattern - Expose Partial
Problem: need to have different customizations in
one part of the view in multiple apps
Example: Address form
One App needs an extra neighborhood field
Another App needs an extra region field
45. Pattern - Expose Partial
Solution:
In Engine, extract view part that needs
customization as a partial.
In App, redefine that partial with customizations.
46. Pattern - Expose Partial
Example:
Define _address_extra_fields partial with empty
content in Engine
Redefine _address_extra_fields in Apps needing
extra fields
47. Pattern - Extension Point
Problem: multiple Apps need to contribute data
to a View in different places
Example: multiple Apps need to add custom
Rows in different spots of a List that comes from
an Engine
48. Pattern - Extension Point
Engine defines only 3 elements in a list (About
Me, News and Noteworthy)
1
2
3
49. Pattern - Extension Point
Solution:
In Engine, add Helper logic that looks up partials in
a specific ext directory, and based on file name (e.g.
row_7.html.erb) insert into the right location in the
View.
In App, define these partials with the right file
names and locations.
53. Pattern - Configurable
Features
Solution:
In Engine, add logic that looks up configuration
options
In App, configure Engine by overriding
configuration options
54. Pattern - Configurable
Features
Example:
Engine defines engine_config.yml
enable_address_extensions: true
enable_address_headers: true
App overrides some options in engine_config.yml
Engine uses config options to customize behavior
using conditionals
55. Rails Engine Costs
Overhead in establishing a new Rails Engine gem
project
Continuous switching between projects and
engines to get work done
Upgrade of ref numbers in Gemfile
56. Rails Engine Benefits
Code reuse across all application layers
Better maintainability due to:
Independent project codebases
Cleanly defined boundaries between projects and
reusable components (engines)
Project tests get smaller and run faster
57. Engines vs Services
Engines are better when:
Reusing small MVC features, especially domain
independent (e.g. Search Map)
Preferring to avoiding network and infrastructure
overhead over establishing a service
Wanting to quickly extract and reuse a feature
58. Engines vs Services
Web Services are better when:
Reusing larger MVC features that depend on
domain data
Need to consume feature in another programming
language or outside the organization boundaries
Need to scale up feature performance
independently of the application (e.g. separate DB)
59. Engines vs Services
To keep an easier to maintain Agile code base,
start simple and then move incrementally
towards a more complex architecture:
Extract an MVC feature as an engine first
Convert engine into a service when the need arises
61. Review
Basics of Rails Engines
Rails Engine Patterns
Typical Development Process
Summary of Benefits and Trade-Offs
62. More Info
http://edgeapi.rubyonrails.org/classes/Rails/
Engine.html
http://andymaleh.blogspot.com/2011/09/more-productiv
http://andymaleh.blogspot.com/2011/09/more-producti
engine.html
http://stackoverflow.com/questions/2964050/rails-engin
2990539