UPDATE 02-13-2011: It appears that approach #2 does have a downside after all. Given my current implementation, dev will not reload the class b/c config.cache_classes is set to false. I might still try this approach using config.to_prepare.. I will update the post when I have a chance to experiment more.
Rails 3 Engines use a standard Rails app directory setup by default, so logically, you might think that you can just drop in /app/controllers/application_controller.rb, and reopen the class. This is not the case. Rails will ignore any files that have the same name as a file in the parent application. Seeing as how every Rails app has “/app/controllers/application_controller.rb”, the engine has no chance of loading this file. Poor little engine. But if your engine has a strong will… I think I can, I think I can.. then there are other ways to get your methods inside the parent app’s ApplicationController. Let’s take a look at some of the choices you have.
Option 1 – ActiveSupport.on_load
Lets start by looking at one of the most popular Rails plugins -> Devise. At the time of this writing, Devise has a helpers module that defines a class method called “define_helpers”. A mapping (user or admin) is passed to the method and several instance methods are defined using class_eval. Then these instance methods are included in the ApplicationController using the “helper_method” function. Check out how this is acheived:
ActiveSupport.on_load(:action_controller) do
helper_method "current_#{mapping}", "#{mapping}_signed_in?", "#{mapping}_session"
end
on_load.. reminds me of something you would see in a javascript library. So when ActiveSupport loads the ActionController, the helper methods are inserted. You could also use a different approach within this block. Let’s say you have defined a module with submodules for ClassMethods and InstanceMethods. You would just extend/include them in the block. The most common approach for Rails 3 engines is using an initializer in the Engine class.
# You can put this in /myengine/app/models/ and it will load it automatically (or put it in your engine lib directory and require it)
module MyModule
module ClassMethods
def some_awesome_class_method
end
end
module InstanceMethods
def some_awesome_instance_method
end
end
end
#/myengine/lib/myengine/engine.rb
require "myengine"
require "rails"
module MyEngine
class Engine < Rails::Engine
initializer 'myengine.app_controller' do |app|
ActiveSupport.on_load(:action_controller) do
extend MyModule::ClassMethods
include MyModule::InstanceMethods
end
end
end
end
There are plenty of variations of the extend/include stuff that use ActiveSupport::Concern, “self.included”, “self.extended”, but they all achieve basically the same thing… adding instance and class methods.
Option 2 – Reopen the class
In the intro of this blog post, I said that Rails will not allow you to load classes from an engine with the same name as the parent app. When it comes to the ApplicationController, I am not sure if it was the intent of the Rails Core to deter you using the same name/format as the application_controller in a regular rails app… there may be use cases that I am not aware of where this is harmful or lacks functionality. It makes sense that other Engine classes with the same name/path should not be loaded since you would want the parent app to override them if it has the same controllers. This could result in clashes and unexpected consequences. But, I see the ApplicationController as a bit of a anomaly in this situation. Unless your parent app has tons of methods in the ApplicationController and the engine ApplicationController is using common method names, I don’t see this as a problem. Use fairly unique method names or prefix the method name with your plugin name if you are worried about it. There is a simple workaround to allow you to use the same format for ApplicationController that you are accustomed to seeing a standard Rails app… just prefix the file name with the plugin name for the ApplicationController. For example: /app/controllers/myplugin_application_controller.rb. Then you can add code to this file, just as I would with a Rails app ApplicationController. Check it out:
#myplugin_application_controller.rb class ApplicationController < ActionController::Base before_filter :this_filter_is_the_best_thing_since_sliced_bread def this_filter_is_the_best_thing_since_sliced_bread end def really_cool_instance_method end def self.really_cool_class_method end end
I think technically you are not reopening this class, since it gets loaded first.. so lets say we are defining the ApplicationController and the parent application is reopening the class. I did some quick tests and the Engine ApplicationController loads before the parent ApplicationController. I also wrote a spec to make sure all filters were intact using this method:
# get a list of the filter names from ApplicationController
filters = ApplicationController._process_action_callbacks.select { |c| c.kind == :before }
filter_names = filters.collect{|filter|filter.filter}
filter_names.include?(:this_filter_is_the_best_thing_since_sliced_bread).should be true #yup
filter_names.include?(:a_filter_defined_in_the_parent_app_controller).should be true #yup



