In the previous two chapters, you’ve seen that the libraries in Rails are mostly built by assembling modules. Back in Rails 2, many of those modules used alias_method_chain to wrap functionality around the methods of their includers. The authors of libraries that extended Rails adopted the same mechanism to wrap their own functionality around the Rails methods. As a result, alias_method_chain was used all over the place, both in Rails and in dozens of third-party libraries.
alias_method_chain was good at removing duplicated aliases, but it also came with a few problems of its own. For a start, alias_method_chain is just an encapsulation of an Around Alias (), and Around Aliases have the subtle problems that you might remember from . To make things worse, alias_method_chain turned out to be too clever for its own good: with all the method renaming and shuffling that was going on in Rails, it could become hard to track which version of a method you were actually calling.
However, the most damning issue of alias_method_chain was that it was simply unnecessary in most cases. Ruby is an object-oriented language, so it provides a more elegant, built-in way of wrapping functionality around an existing method. Think back to our example of adding enthusiasm to the greet method:
| module Greetings |
| def greet |
| "hello" |
| end |
| end |
| |
| class MyClass |
| include Greetings |
| end |
| |
| MyClass.new.greet # => "hello" |
Instead of using aliases to wrap additional functionality around greet, you can just redefine greet in a separate module and include that module instead:
| module EnthusiasticGreetings |
| include Greetings |
| |
| def greet |
| "Hey, #{super}!" |
| end |
| end |
| |
| class MyClass |
| include EnthusiasticGreetings |
| end |
| |
| MyClass.ancestors[0..2] # => [MyClass, EnthusiasticGreetings, Greetings] |
| MyClass.new.greet # => "Hey, hello!" |
The chain of ancestors of MyClass includes EnthusiasticGreetings and then Greetings, in that order. That’s why by calling greet, you end up calling EnthusiasticGreetings#greet, and EnthusiasticGreetings#greet can in turn call into Greetings#greet with super. This solution is not as glamorous as alias_method_chain, but it’s simpler and all the better for it. Recent versions of ActiveRecord::Validations acknowledge that simplicity by using a regular override instead of alias_method_chain:
| module ActiveRecord |
| module Validations |
| # The validation process on save can be skipped by passing |
| # <tt>validate: false</tt>. |
| # The regular Base#save method is replaced with this when the |
| # validations module is mixed in, which it is by default. |
| def save(options={}) |
| perform_validations(options) ? super : false |
| end |
| |
| # Attempts to save the record just like Base#save but will raise |
| # a +RecordInvalid+ exception instead of returning +false+ if |
| # the record is not valid. |
| def save!(options={}) |
| perform_validations(options) ? super : raise(RecordInvalid.new(self)) |
| end |
| |
| def perform_validations(options={}) |
| # ... |
Validation#save performs the actual validation (by calling the private method perform_validations). If the validation succeeds, then it proceeds with the normal save code in ActiveRecord::Base by calling super. If the validation fails, then it returns false. Validation#save! follows the same steps, except that it raises an exception if the validation fails.
These days, Rails barely ever uses alias_method_chain. You can still find this method called inside Active Support and some third-party libraries, but there is no trace of it in libraries such as Active Record. The once-popular alias_method_chain has nearly disappeared from the Rails environment.
However, there is still one case where you might argue that alias_method_chain works better than its object-oriented alternative. Let’s look at it.
Let’s add a twist to our ongoing greet method example: instead of defining greet in a module, let’s assume it’s defined directly in the class.
| class MyClass |
| def greet |
| "hello" |
| end |
| end |
| |
| MyClass.new.greet # => "hello" |
In this case, you cannot wrap functionality around greet by simply including a module that overrides it:
| module EnthusiasticGreetings |
| def greet |
| "Hey, #{super}!" |
| end |
| end |
| |
| class MyClass |
| include EnthusiasticGreetings |
| end |
| |
| MyClass.ancestors[0..2] # => [MyClass, EnthusiasticGreetings, Object] |
| MyClass.new.greet # => "hello" |
The code above shows that when you include EnthusiasticGreetings, that module gets higher than the class in the class’s chain of ancestors. As a result, the greet method in the class overrides the greet method in the module, instead of the other way around.
You could solve this problem by extracting greet from MyClass into its own module, like the Greetings module in the previous section. If you do that, you’ll be able to insert an intermediary module like EnthusiasticGreetings in the chain and use the override-and-call-super technique, just as we did back then. However, you might be unable to do that—for example, because MyClass is part of a library such as Rails, and you’re extending that library rather than working directly on its source code. This limitation is the main reason why many Rubyists still use alias_method_chain when they extend Rails.
However, Ruby 2.0 came with an elegant solution for this problem in the form of Module#prepend:
| module EnthusiasticGreetings |
| def greet |
| "Hey, #{super}!" |
| end |
| end |
| |
| class MyClass |
| prepend EnthusiasticGreetings |
| end |
| |
| MyClass.ancestors[0..2] # => [EnthusiasticGreetings, MyClass, Object] |
| MyClass.new.greet # => "Hey, hello!" |
This is a Prepended Wrapper (), a modern alternative to Around Aliases (). Because we used prepend, the EnthusiasticGreetings#greet got lower than MyClass#greet in MyClass’s chain of ancestors, so we went back to the usual trick of overriding greet and calling super.
As I write, Rails is not using Module#prepend yet, because it’s still aiming to be compatible with Ruby 1.9. When Rails eventually drops this constraint, I expect that prepend will make its appearance in Rails and its extensions. At that point, there will be no urgent reason to call alias_method_chain anymore.