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.

  • Share/Bookmark

Leave a Reply