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.
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
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.
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
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
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
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
This is because
ApplicationController gets reloaded before each request during
development. Without this block, only the first request would see the