Rails provides a nifty utility in ActiveSupport called
alias_method_chain. For those not familiar with it, it simplifies the task of “replacing” an already defined method with an augmented one. The new method is aliased to the name of the original method and the original method is aliased to some other name in order that it may still be referenced.
More succinctly, the following call:
is effectively the same as:
Fig. 1 illustrates how the
alias_method_chain call changes references to the method definitions like so:
Now the original method defined as
:number_printer is referenced as
:number_printer now points to the method definition for
:number_printer_with_filter, which can be referenced as either
This implies that prior to the execution of the
alias_method_chain call, you must define both methods
The ninja-decorators project relies heavily on
alias_method_chain and its usage will be used as the example throughout the remainder of the article. ninja-decorators gives you
around_filter functionality outside of Rails controllers. With these methods you can handle cross-cutting concerns in a class located elsewhere in your Rails app or without having to use Rails at all. Using the standard examples of security and logging as cross-cutting concerns, we have something like the following:
Here, we want
:log_around to decorate
:secure_around applied. Internally,
around_filter delegates to
alias_method_chain to handle method decoration.
The problem with the implementation of
alias_method_chain is one of definition order with regards to its two internal
alias_method calls. If the new head of the chain is an enhancement of an existing method in the chain, there likely exists a coupling between the two. Since the
alias_method_chain call is effectively atomic, however, this complicates how the two methods reference each other. Fig. 2 shows the intermittent states between each of the two
alias_method calls made internally by
:number_printer_without_filter will not exist until after the
alias_method_chain call is complete.
:number_printer_with_filter must exist before the
alias_method_chain call can begin, otherwise the second
alias_method call made internally will fail. As a consequence,
:number_printer_with_filter must call
:number_printer_without_filter dynamically. As long as you only use one level of
alias_method_chain calls, this isn’t a problem. With multiple levels of chaining, however, dynamic calls like this fall apart.
To make the discussion a little more concrete, we’ll use the following example adapted from the ninja-decorators project. It is a bit contrived, but should serve well enough as a basis for discussion.
:square_printer are two simple methods. They take a number in and print out its value or its square, respectively.
:increment_filter is a simple “around filter”; it augments a method by incrementing the input argument by 1 before executing the original method. Running both methods will produce the following in IRB:
around_filter is where all the hard work is being done and is where
alias_method_chain is employed. It takes as its arguments a filter method name and a list of method names to decorate with that filter. For each method to decorate it defines the required “with” method for
alias_method_chain. This newly defined method will call the filter method (
:increment_filter in this case), which will in turn call the original, undecorated method (
:square_printer_without_increment_filter) as a block. Once the “with” method is defined,
alias_method_chain is called so that the original method name can be used to transparently call the newly decorated method.
While convoluted (don’t worry, it’s wrapped up a library), this approach will work dandily until you need to start decorating a method more than once. For the sake of the example, we’ll pretend that we actually want to increment each input argument as two. In reality, well’d likely want to apply a completely different filter. The outcome is precisely the same, but to keep things simple, we’ll just apply the
Running this through IRB again, we’d likely expect to see
:number_printer print out
2 + num for argument
num. Instead, the session looks like this:
That’s the polite way of telling you that you have infinite recursion, not the expected value of 5. The issue is that each
around_filter call defines a new “with” method that calls the “without” method dynamically. Calling a method by name with
send, however, only calls the one at the current lexical scope. Meanwhile, each call to
alias_method_chain changes the alias target of the “without” method. As such, we have the execution flow illustrated in Fig. 3 rather than the expected one in Fig. 4.
The key thing to note about the code behavior observed in Fig. 3 is that the second
alias_method_chain call will alias
:number_printer_without_filter, just like the first call will. However, after the first
alias_method_chain call is made,
:number_printer is aliased to the definition
:number_printer_with_filter (as seen in Fig. 2). Calling
:number_printer at this point will call
:number_printer_with_filter because of the decoration and, subsequently,
:number_printer_with_filter will call
:number_printer_without_filter, the latter of which is now pointing at the definition of
:number_printer_with_filter as well. That’s a lot of words to say that we end up in a situation where
:number_printer_with_filter calls itself inadvertently and there’s no base case to break out.
There is no* clean way around this with
alias_method_chain. It’s a classic chicken-and-egg situation. The best that can be done is for
around_filter to maintain a stack of
UnboundMethod objects in a class instance variable. While doable, this resource management is error-prone and would have to be replicated by any method affected by the problem. Effectively, it is the same process the VM would normally perform in managing stack frames at each recursion level, so it’s best to let the VM do it.
The problem can be averted with a minor change in
alias_method_chain. The idea is to yield to a block between the two
alias_method calls, allowing the proper formation of closures to the “without” method. Unfortunately, this is not backwards-compatible with Rails because
alias_method_chain yields to a block elsewhere for reasons that are not quite clear to me (I believe it’s to handle method names with punctuation).
A simplified definition is thus:
The block passed to alias_method_chain can then take care of the creation of the “with” method, which will have access to the “without” method at the current level. Breaking away from
around_filter, we can more easily see how nested
alias_method_chain calls work with the new definition:
In this admittedly convoluted example, the block passed to the
alias_method_chain calls is built up as a proc first. This allows us to make the same
alias_method_chain calls without needing to duplicate code. The proc gets a reference to
:number_printer_without_filter and calls it within the newly defined
:number_printer_with_filter, which for simplicity in the example, provides the same behavior that
:increment_filter previously did. This forms a closure and lets each level of “with” and “without” methods to pair up, subsequently avoiding the infinite recursion problem when using just
Running in IRB now, we get the expected behavior of print out of
2 + num for argument
num, rather than the stack overflow exception we previously experienced:
I began writing this post just to document the changes necessary to alias_method_chain in order to make ninja-decorators work. If this work could make its way back into Rails core, great. Otherwise, it serves as a decent rationale document. If you’ve run into similar issues yourself, you should now know why and how to work around them. One issue not addressed here is reordering the chain or removing links from the chain. Since each link has a tight coupling at the time of definition, altering the chain via anything other than an append/prepend may be confusing.
* I suspect someone much smarter than me knows a way. After a couple days on the issue, I couldn’t come up with anything.