63
Rails Engine Patterns Andy Maleh Software Engineer for Groupon (Oct 2011 - Oct 2012) Former Senior Consultant for Obtiva

Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Embed Size (px)

Citation preview

Page 1: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Rails Engine PatternsAndy Maleh

Software Engineer for Groupon (Oct 2011 - Oct 2012)

Former Senior Consultant for Obtiva

Page 2: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Problem

Difficulty reusing functionality cutting across:Difficulty reusing functionality cutting across:

ModelsModels

ViewsViews

ControllersControllers

Assets (JS, CSS, Images)Assets (JS, CSS, Images)

Duplication across all web application layers.Duplication across all web application layers.

Page 3: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Courtesy of © 2002-2011 National Collegiate Scouting Association - All Rights ReservedCourtesy of © 2002-2011 National Collegiate Scouting Association - All Rights Reserved

Page 4: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Solution

Break common behavior into Rails EnginesBreak common behavior into Rails Engines

Customize models/controllers/helpers in each Customize models/controllers/helpers in each project where needed by reopening classesproject where needed by reopening classes

Customize Rails views in each project as Customize Rails views in each project as needed by overriding templatesneeded by overriding templates

Link to Rails Engines in Gemfile via Git repoLink to Rails Engines in Gemfile via Git repo

Page 6: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Courtesy of © 2002-2011 National Collegiate Scouting Association - All Rights ReservedCourtesy of © 2002-2011 National Collegiate Scouting Association - All Rights Reserved

Page 7: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Courtesy of © 2002-2011 National Collegiate Scouting Association - All Rights ReservedCourtesy of © 2002-2011 National Collegiate Scouting Association - All Rights Reserved

Page 8: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

What is a Rails Engine?

Ruby Gem Ruby Gem

+ +

MVC stack elementsMVC stack elements

Page 9: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

What is a Rails Engine?

Rails Engines let applications reuse:Rails Engines let applications reuse:

Models / Views / Controllers / HelpersModels / Views / Controllers / Helpers

Assets (JS, CSS, Images)Assets (JS, CSS, Images)

RoutesRoutes

Rake tasksRake tasks

GeneratorsGenerators

Page 10: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

What is a Rails Engine?

Rails Engines let applications reuse:Rails Engines let applications reuse:

InitializersInitializers

RSpec / CucumberRSpec / Cucumber

Rack application with middleware stackRack application with middleware stack

Migrations and seed dataMigrations and seed data

LibrariesLibraries

Page 11: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Engine Definition

An engine structure is similar to a Rails app An engine structure is similar to a Rails app having app, config, lib, spec, features, etc…having app, config, lib, spec, features, etc…

lib/engine_name.rb (engine loader)lib/engine_name.rb (engine loader)

lib/engine_name/engine.rb (engine class)lib/engine_name/engine.rb (engine class)

To reuse engine, generate gemspec (using To reuse engine, generate gemspec (using “Jeweler or Gemcutter”)“Jeweler or Gemcutter”)

Page 13: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

lib/engine_name/engine.rb

Page 14: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Isolated Engines

To avoid Ruby namespace clash with To avoid Ruby namespace clash with Models/Helpers/Controllers, you can define an Models/Helpers/Controllers, you can define an isolated (namespaced) engine.isolated (namespaced) engine.

Add “isolate_namespace MyEngine” to engine Add “isolate_namespace MyEngine” to engine class bodyclass body

For more details, go to For more details, go to http://edgeapi.rubyonrails.org/classes/Rails/http://edgeapi.rubyonrails.org/classes/Rails/Engine.htmlEngine.html

Page 15: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Engine Consumption

Reference engine via Gemfile as a Ruby gem or Reference engine via Gemfile as a Ruby gem or Git repo hosted gemified project:Git repo hosted gemified project:

Courtesy of © 2002-2011 National Collegiate Scouting Association - All Rights ReservedCourtesy of © 2002-2011 National Collegiate Scouting Association - All Rights Reserved

Page 16: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Engine Consumption

To avoid frequent updates of ref (outsider advice):To avoid frequent updates of ref (outsider advice):

Courtesy of © 2002-2011 National Collegiate Scouting Association - All Rights ReservedCourtesy of © 2002-2011 National Collegiate Scouting Association - All Rights Reserved

Page 17: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Load Order

Typically Rails app files load first before Engine Typically Rails app files load first before Engine files. files.

Strongly recommended to reverse so that Strongly recommended to reverse so that engine’s code is customizable in appengine’s code is customizable in app

Page 18: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Load Order

Reversing load order can happen in one of two Reversing load order can happen in one of two ways:ways:

Patching “active_support/dependencies.rb” in Patching “active_support/dependencies.rb” in Rails 3.1- (see next slide)Rails 3.1- (see next slide)

Adjusting railties_order in Rails 3.2+Adjusting railties_order in Rails 3.2+

Page 20: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Engine Configuration

Engines can be configured to customize rack Engines can be configured to customize rack middleware, load paths, generators, and Rails middleware, load paths, generators, and Rails component paths. More details at: component paths. More details at: httphttp://edgeapi.rubyonrails.org/classes/Rails/://edgeapi.rubyonrails.org/classes/Rails/Engine.htmlEngine.html

Page 21: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

View and Asset Customization

Engine View and Asset presentation can be Engine View and Asset presentation can be customized by redefining files in Rails Appcustomized by redefining files in Rails App

Customizations Customizations completely overridecompletely override files in Engine files in Engine

Examples of customizable View and Asset files:Examples of customizable View and Asset files:ERB/HamlERB/Haml

JSJS

CSSCSS

ImagesImages

Page 22: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Ruby Code Customization

Model/Helper/Controller behavior can be Model/Helper/Controller behavior can be customized be redefining .rb files in Rails Appcustomized be redefining .rb files in Rails App

Customizations get Customizations get mixed inmixed in with files in with files in EngineEngine

This allows:This allows:

Adding new methods/behaviorAdding new methods/behavior

Replacing existing methodsReplacing existing methods

Extending existing methods (alias_method_chain)Extending existing methods (alias_method_chain)

Page 23: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Ruby Code Customization Example

Page 24: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Ruby Code Customization Example

Page 25: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

View Customization Example

Page 26: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

View Customization Example

Page 27: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Recommended Engine Code Management• Each Engine lives in own Repo independent of Each Engine lives in own Repo independent of

Rails AppRails App

• Each Engine has its own Gem dependencies Each Engine has its own Gem dependencies (Gemfile)(Gemfile)

• Engines preferably share the same Ruby Engines preferably share the same Ruby version as Rails Appversion as Rails App

Page 28: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Typical Development Process1.1.Make changes in engine, rake, and commit Make changes in engine, rake, and commit

obtaining a new GIT refobtaining a new GIT ref

2.2.Update Gemfile in app with new git ref, run Update Gemfile in app with new git ref, run “bundle install” (getting ride of symlink)“bundle install” (getting ride of symlink)

3.3.Rake and commit changes in app.Rake and commit changes in app.

4.4.If more changes in engine are needed go back If more changes in engine are needed go back to step 1to step 1

Page 29: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Improved Development Process1.1. Work in app and engine until done WITHOUT running Work in app and engine until done WITHOUT running

“bundle install”“bundle install”

2.2. Rake and commit changes in engine obtaining a new git Rake and commit changes in engine obtaining a new git refref

3.3. Update Gemfile in app with git ref, run “bundle install”Update Gemfile in app with git ref, run “bundle install”

4.4. Rake and commit changes in app Rake and commit changes in app

Page 30: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Engines Reuse Engines

Rails engines can reuse other Rails enginesRails engines can reuse other Rails engines

Page 31: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Engines Reuse Engines

When multiple levels of depth are involved, When multiple levels of depth are involved, commit repos and update Gemfile from the commit repos and update Gemfile from the bottom up bottom up

Example:Example: Engine 2 => Engine 1 => AppEngine 2 => Engine 1 => App

1.1. Commit in Engine 2Commit in Engine 2

2.2. Update Gemfile in Engine 1 and commitUpdate Gemfile in Engine 1 and commit

3.3. Update Gemfile in App and commitUpdate Gemfile in App and commit

Page 32: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Rails Engine Patterns

Goals: Goals:

Keep engine code agnostic of app customizationsKeep engine code agnostic of app customizations

Prevent bi-directional coupling to simplify Prevent bi-directional coupling to simplify reasoning about codereasoning about code

Avoid app dependent conditionals to improve Avoid app dependent conditionals to improve code maintainabilitycode maintainability

Page 33: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Common Domain

Problem: Multiple Rails Apps need to share a basic Problem: Multiple Rails Apps need to share a basic domain model including default CRUD and domain model including default CRUD and presentation behaviorpresentation behavior

Example: need to reuse address model and form Example: need to reuse address model and form entry behaviorentry behavior

Page 34: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Common Domain

Solution:Solution:

In engine, include basic domain models (base In engine, include basic domain models (base behavior, common associations) with their views, behavior, common associations) with their views, CRUD controllers, and routes. CRUD controllers, and routes.

In each app, define domain model specialized In each app, define domain model specialized behavior, extra associations, and custom viewsbehavior, extra associations, and custom views

Make sure routes are declared with Make sure routes are declared with MyEngine::Engine.routes.draw (not Rails App MyEngine::Engine.routes.draw (not Rails App name)name)

Page 35: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Common Domain

Example: address.rb, addresses_controller.rb, Example: address.rb, addresses_controller.rb, address route, and _address.html.erbaddress route, and _address.html.erb

Page 36: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Expose Helper

Problem: need to customize presentation Problem: need to customize presentation logiclogic for a for a view in one app only, but keep the same logic in view in one app only, but keep the same logic in othersothers

Example: Example: One App needs to hide address1 and county for non-One App needs to hide address1 and county for non-government users.government users.

Other Apps wants to keep the fields displayed.Other Apps wants to keep the fields displayed.

You might start by overriding view, but then realize it You might start by overriding view, but then realize it is duplicating many elements just to hide a couple is duplicating many elements just to hide a couple fields.fields.

Page 37: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Expose Helper

Solution: Solution:

In Engine, extract helper logic that needs In Engine, extract helper logic that needs customization into its own helper. customization into its own helper.

In App, redefine that new helper with In App, redefine that new helper with customizations.customizations.

Page 38: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Expose Helper(view in Engine)

Page 39: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Expose Helper (trying to customize view in App)

Page 40: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Expose Helper

Remove custom view from AppRemove custom view from App

Use requires_extended_address? helper in Engine Use requires_extended_address? helper in Engine wherever the App used government_user? wherever the App used government_user?

In Engine, define requires_extended_address? to In Engine, define requires_extended_address? to return truereturn true

In App, define requires_extended_address? to return In App, define requires_extended_address? to return government_user?government_user?

Page 41: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Expose Helper(view + helper in Engine)

Page 42: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Expose Partial

Problem: need to have different customizations Problem: need to have different customizations in one part of the view in multiple appsin one part of the view in multiple apps

Example: Address form Example: Address form

One App needs an extra neighborhood field One App needs an extra neighborhood field

Another App needs an extra region fieldAnother App needs an extra region field

Page 43: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Expose Partial

Example App 1:Example App 1:

Page 44: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Expose Partial

Example App 2:Example App 2:

Page 45: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Expose Partial

Solution: Solution:

In Engine, extract view part that needs In Engine, extract view part that needs customization as a partial. customization as a partial.

In App, redefine that partial with customizations.In App, redefine that partial with customizations.

Page 46: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Expose Partial

Example:Example:

Define _address_extra_fields partial with empty Define _address_extra_fields partial with empty content in Enginecontent in Engine

Redefine _address_extra_fields in Apps needing Redefine _address_extra_fields in Apps needing extra fieldsextra fields

Page 47: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Extension Point

Problem: multiple Apps need to contribute data Problem: multiple Apps need to contribute data to a View in different placesto a View in different places

Example: multiple Apps need to add custom Example: multiple Apps need to add custom Rows in different spots of a List that comes Rows in different spots of a List that comes from an Enginefrom an Engine

Page 48: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Extension Point

Engine defines only 3 elements in a list (About Engine defines only 3 elements in a list (About Me, News and Noteworthy)Me, News and Noteworthy)

1

2

3

Page 49: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Extension Point

Solution: Solution:

In Engine, add Helper logic that looks up partials In Engine, add Helper logic that looks up partials in a specific ext directory, and based on file name in a specific ext directory, and based on file name (e.g. row_7.html.erb) insert into the right location (e.g. row_7.html.erb) insert into the right location in the View. in the View.

In App, define these partials with the right file In App, define these partials with the right file names and locations.names and locations.

Page 50: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Extension Point

App 1 adds “nav_bar_list_ext/_row_1.html.erb”App 1 adds “nav_bar_list_ext/_row_1.html.erb”

1

2

3

4

Page 51: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Extension Point

App 2 adds “nav_bar_list_ext/_row_4.html.erb”App 2 adds “nav_bar_list_ext/_row_4.html.erb”

1

2

3

4

Page 52: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Configurable Features

Problem: different apps need different features Problem: different apps need different features from an engine in different combinationsfrom an engine in different combinations

Page 53: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Configurable Features

Solution: Solution:

In Engine, add logic that looks up configuration In Engine, add logic that looks up configuration optionsoptions

In App, configure Engine by overriding In App, configure Engine by overriding configuration optionsconfiguration options

Page 54: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Pattern - Configurable Features

Example: Example:

Engine defines engine_config.yml Engine defines engine_config.yml

enable_address_extensions: trueenable_address_extensions: true

enable_address_headers: trueenable_address_headers: true

App overrides some options in engine_config.ymlApp overrides some options in engine_config.yml

Engine uses config options to customize behavior Engine uses config options to customize behavior using conditionalsusing conditionals

Page 55: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Rails Engine Costs

Overhead in establishing a new Rails Engine Overhead in establishing a new Rails Engine gem projectgem project

Continuous switching between projects and Continuous switching between projects and engines to get work doneengines to get work done

Upgrade of ref numbers in GemfileUpgrade of ref numbers in Gemfile

Page 56: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Rails Engine Benefits

Code reuse across all application layersCode reuse across all application layers

Better maintainability due to:Better maintainability due to:

Independent project codebases Independent project codebases

Cleanly defined boundaries between projects and Cleanly defined boundaries between projects and reusable components (engines)reusable components (engines)

Project tests get smaller and run fasterProject tests get smaller and run faster

Page 57: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Engines vs Services

Engines are better when:Engines are better when:

Reusing small MVC features, especially domain Reusing small MVC features, especially domain independent (e.g. Search Map)independent (e.g. Search Map)

Preferring to avoiding network and infrastructure Preferring to avoiding network and infrastructure overhead over establishing a serviceoverhead over establishing a service

Wanting to quickly extract and reuse a featureWanting to quickly extract and reuse a feature

Page 58: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Engines vs Services

Web Services are better when:Web Services are better when:

Reusing larger MVC features that depend on Reusing larger MVC features that depend on domain datadomain data

Need to consume feature in another Need to consume feature in another programming language or outside the programming language or outside the organization boundariesorganization boundaries

Need to scale up feature performance Need to scale up feature performance independently of the application (e.g. separate independently of the application (e.g. separate DB)DB)

Page 59: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Engines vs Services

To keep an easier to maintain Agile code base, To keep an easier to maintain Agile code base, start simple and then move incrementally start simple and then move incrementally towards a more complex architecture:towards a more complex architecture:

Extract an MVC feature as an engine firstExtract an MVC feature as an engine first

Convert engine into a service when the need Convert engine into a service when the need arisesarises

Page 60: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Questions & Answers

??????

Page 61: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Review

Basics of Rails Engines Basics of Rails Engines

Rails Engine Patterns Rails Engine Patterns

Typical Development ProcessTypical Development Process

Summary of Benefits and Trade-OffsSummary of Benefits and Trade-Offs

Page 63: Revised Rails Engine Patterns for Montreal.rb meetup Oct 16, 2012

Contact

Code Painter Blog: Code Painter Blog: http://andymaleh.blogspot.comhttp://andymaleh.blogspot.com

Twitter: Twitter: @AndyMaleh@AndyMaleh

LinkedIn: Look up “Andy Maleh”LinkedIn: Look up “Andy Maleh”