While converting Clearance to a Rails engine was easy, once we were there, we found it wasn’t Valhalla.
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.