Tips For Writing Your Own Rails Engine

Dan Croak

While converting Clearance to a Rails engine was easy, once we were there, we found it wasn’t Valhalla.

Viking Funeral

We fixed the bugs while using the engine internally on a few apps. Here are the lessons we learned. Keep them in mind if you’re thinking of writing your own engine.

Routes precedence

As developers, we want routes in our app to take precedence over routes in the engine. That is not the default behavior.

To get around that, we came up with this hack. (credit to Nick Quaranto, a.k.a. qrush a.k.a. Internbot)

class ActionController::Routing::RouteSet
  def load_routes_with_clearance!
    clearance_routes = File.join(File.dirname(__FILE__),
                        *%w[.. config clearance_routes.rb])
    unless configuration_files.include? clearance_routes
      add_configuration_file(clearance_routes)
    end
    load_routes_without_clearance!
  end

  alias_method_chain :load_routes!, :clearance
end

Rather than using the Rails engine convention of naming the routes file vendor/gems/clearance/config/routes.rb, we named it vendor/gems/clearance/config/clearance_routes.rb so it won’t be automatically loaded. Then we alias_method_chain around some ActionController internals.

Ugly stuff, but effective and solved a blocker for releasing as an engine.

Cached classes in development

In development, all your classes are constantly reloaded. This makes sense for your app, but not for your engine classes or modules.

We added unloadable to our Clearance module to fix this. Again, credit goes to Nick.

class Clearance::SessionsController < ApplicationController
  unloadable
end

If you see errors like the following, you might consider a similar approach.

has been removed from the module tree but is still active

Namespacing controllers

We decided namespacing controllers was a good convention for our engine.

When we need to override something in the engine, this gives us clear routes separation…

# in the engine
ActionController::Routing::Routes.draw do |map|
  map.resources :users, :controller => 'clearance/users' do |users|
    users.resource :password,
      :controller => 'clearance/passwords',
      :only => [:create, :edit, :update]
end

# in the app
ActionController::Routing::Routes.draw do |map|
  map.resources :users
end

… and clean subclassing:

class UsersController < Clearance::UsersController
  def edit
    ...
  end
end

Helper inclusion

Tammer has also recently been extracting the announcement code from Hoptoad into an engine. He found that helper :all in the controller doesn’t find engine helpers.

Within the engine’s init.rb, he hooked his engine’s AnnouncementsHelper into the app’s ApplicationHelper like so:

config.to_prepare do
  ApplicationController.helper(AnnouncementsHelper)
end

Note that any code that extends classes from the application (such as ApplicationController) must be wrapped in a to_prepare block.

This is because ApplicationController gets reloaded before each request during development. Without this block, only the first request would see the AnnouncementsHelper methods.