This talk covers a successful utilization of Rails Engines to share features that cut across the layers of MVC in different Rails 3 projects. Rails Engines thus provide the best of both worlds: improved productivity by reusing MVC code (including assets like Javascript, CSS, and Images) and better flexibility by allowing different applications to customize behavior as needed without reliance on application-dependent conditionals. Rails Engine patterns will be provided to guide developers on how to leverage Rails Engines' reusability and flexibility without sacrificing maintainability.
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
5. Example
Common
Domain
Rails Engine
Search Map
Rails Engine
High School Public Athlete
Recruiting Profiles Recruiting
Rails App Rails App Rails App
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 (read online instructions)
lib/engine_name/engine.rb (read online
instructions)
To reuse engine, use “jeweler” gem to generate
gemspec (read online instructions)
15. Load Order
Typically Rails app files load first before Engine
files.
Strongly recommended to reverse so that
engine’s code is customizable in app
16. 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+
17.
18. 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)
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
24. 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
25. 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
26. Improved Productivity via
Symlinking
Multiple engine dependencies can hamper
productivity when frequently going back and
forth between engines and app
Engine gems installed via bundler can be
symlinked to allow continuous development until
done with both app and
engine:http://andymaleh.blogspot.com/2011/09/
more-productive-rails-engine.html
27. Improved Development
Process
1. Open Rails app and symlink all engines “rake
engine:symlink[engine_name]”
2. Work in app and engine until done WITHOUT running
“bundle install”
3. Rake and commit changes in engine obtaining a new git
ref
4. Update Gemfile in app with git ref, run “bundle install”
(getting ride of symlink)
5. Rake and commit changes in app
28. Engines Reuse Engines
Rails engines can reuse other Rails engines
Common
Domain
Rails Engine
Search Map
Rails Engine
High School Public Athlete
Recruiting Profiles Recruiting
Rails App Rails App Rails App
29. 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
30. 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/Engi
ne.html
31. Isolated Engines
To avoid Ruby namespace clash with
Models/Helpers/Controllers, you can define an
isolated namespaced engine.
For more details, go to
http://edgeapi.rubyonrails.org/classes/Rails/Engi
ne.html
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
Rails.application.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 on every
commit (minimized with symlinking)
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
Improved Productivity Tips
Summary of Benefits and Trade-Offs
62. More Info
http://edgeapi.rubyonrails.org/classes/Rails/Engi
ne.html
http://andymaleh.blogspot.com/2011/09/more-
productive-rails-engine.html
http://stackoverflow.com/questions/2964050/rail
s-engines-extending-
functionality/2990539#2990539