Rails Voodoo: How does before_filter work?
Sometimes I am working with Rails and just take things for granted. Like before_filter and after_filter. For those who don’t use rails, basically, before_filter and after_filter are called before and after actions are processed. So instead of having to look-up my user from the database in every single action, I can create a before_filter that handles it for me, making the user available in all my actions.
Now what is cool about this is that actions have no special creation system. You just create a new class method, and *bam*, you have a new action for your controller. In that case, what sort of voodoo is going on to prepend and append the filters to our methods? Some sort of ‘alias’ trickery? But that won’t work unless the filters are evaluated after the methods, and that certainly isn’t true for our filters…
Let’s see how deep the rabbit hole goes. Looking at ActionController::Base, we find the perform_action method, which, as you can imagine, performs an action. After extracting the action name and determining whether it is a public action, it tries to ‘send’ the action. Herm, no reference to filters anywhere here.
Next, let’s look at ActionController::Filter. Here, we find a base Filter type, which is a ActiveSupport::Callbacks::Callback. Might as well take a look at that… and all we find is a typical callback object. Well, that got us nowhere. Looking back at the filters source, we can see that when the module is included, it stuffs a whole mess of methods in the base. And that is where the gold is. Look down at the InstanceMethods module. See right there, squeezed in the self.included(base), we find it. The meat and potatoes. alias_method_chain. It seems to be aliasing process and perform_action — the heart of ActionController. But what the hell is alias_method_chain? A bit of poking and prodding and googling, and we find this nice little article which explains to us that alias_method_chain :perform_action, :filters really ends up as alias_method :perform_action_with_no_filters, :perform_action and alias_method :perform_action, :perform_action_with_filters. Likewise for process. Right below, what do we find?
protected def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc: @before_filter_chain_aborted = false process_without_filters(request, response, method, *arguments) end def perform_action_with_filters call_filters(self.class.filter_chain, 0, 0) end private def call_filters(chain, index, nesting) index = run_before_filters(chain, index, nesting) aborted = @before_filter_chain_aborted perform_action_without_filters unless performed? || aborted return index if nesting != 0 || aborted run_after_filters(chain, index) end
And there we go. A little inheritance and alias magic, and the case is closed.