Thursday, May 20, 2010

Rails alias_method_chain explained with pictures!

I've been meaning to do this for a while because I keep coming across alias_method and alias_method_chain in Rails code. I understood what was going on, but felt that I never grokked it. So, I finally mapped out what happens, drew some pictures, and, voila! Enlightenment!

Since I firmly believe that the best way to learn something is to teach it, I thought I would document what I learned in the hopes that it will be beneficial to someone else and firmly cement my understanding. So without further ado:

It is important to keep in mind that a method name is essentially a constant symbol and points to a block of code. "alias_method" is a Ruby class Module method with the signature:

alias_method(new_name, old_name)

And, "alias_method_chain" looks like this:

alias_method_chain :method_name, :feature

That's the easy part.

For the purpose of this example, I'm examining the behavior of:

alias_method_chain :render, :feature

The alias_method method makes a copy of the method block and assigns the new_name constant to point to the copy of the method block. So, it looks something like this:

Here's the method render and its method block:


After we call alias_method :render_without_feature, :render we have the following:
At this point, if we wanted to, we could redefine the render method, which would replace the original {render} block with new functionality. And, as it is often explained, the original functionality is still available at the render_without_feature method.

However, as it is often explained, what is desired in Rails library code is to redefine an existing method while saving the original functionality under a new name. Therefore, what we see in Rails code was often:

alias_method :render_without_feature, :render
alias_method :render, :render_with_feature


We already looked at the first line. Let's look at the second line.

The second line is assigning the constant "render" to a copy of the "render_with_feature" functionality. It looks like this:
Now, render will call the code block with the new feature and render_without_feature will call the original render block.

This is the key to the entire Rails helper, alias_method_chain, which takes the name of the original method and the name of the feature and does this mapping for us.

Therefore, the two alias_method calls above can be replaced with the following:

alias_method_chain :render, :feature

You, the fine engineer, will define a method render_with_feature and the render symbol will point to it. And, after the alias_method_chain call, you will have a new render_without_feature method created for you.

I find all of this rather elegant and hope this has been helpful

9 comments:

  1. I couldn't help but notice that you wrote "So without further adieu" when really it should be "So without further ado" - adieu is French for goodbye.

    Other than your blog has been most helpful :)

    ReplyDelete
  2. Absolutely correct and thank you for pointing that out. The incorrect phrase has been corrected.

    I'm glad the blog has been helpful!

    ReplyDelete
  3. Thanks for the post, Peter. That clears up a lot of the magic that's going with this method.

    Question: Are you able to yield the "original" render method from within render_with_feature block?

    For example:

    alias_method_chain :render, :logging

    def render_with_logging
    # log the start of a render

    render

    # log the end of render
    end

    Thanks!

    ReplyDelete
  4. Thanks for the post, Peter. That clears up a lot of the magic that's going with this method.

    Question: Are you able to yield the "original" render method from within render_with_feature block?

    For example:

    alias_method_chain :render, :logging

    def render_with_logging
    # log the start of a render

    render

    # log the end of render
    end

    Thanks!

    ReplyDelete
    Replies
    1. Hi Josh. Good question, thanks for bringing it up.

      Since the "alias_method_chain" assigns the original method name to function called "#{method_name}_with_#{feature}" (in this case "render_with_logging"), a call to "render" is the same as a call to "render_with_logging" and there would be a recursive series of call that would probably blow the call stack.

      To get to the original "render" function, you can use the "render_without_logging" method name that will be created as part of the "alias_method_chain" processing.

      So you probably want something like:

      def render_with_logging
      # log the start of render
      render_without_logging
      # log the end of render
      end

      Delete
    2. Didn't finish the thought.

      Then, when you call "render" you get the "render_with_logging" behavior.

      Will that do what you want?

      Delete
  5. Yessir! That will do exactly what I want. With all these aliases floating around, even your brilliant explanation and images weren't enough to make me understand. :)

    The plan is to use this for memcaching expensive calculation methods but making it easier to wrap up the "fetch" blocks.

    Thanks again for taking the time to write the original post and to reply to me.

    Josh

    ReplyDelete
  6. Very helpful and perfectly clear.
    Since I was observing this from the eye of a newbie, I think it's worth mentioning that "feature" relates to the added functionality when wanting to override a method but still allow it to be referenced in case it's needed.

    Thank you very much for this post!

    ReplyDelete