In , I showed you a snippet of code from an old version of Rails…minus a few interesting lines. Here is the same code again, with those lines now visible and marked with arrows:
| module ActiveRecord |
| module Validations |
| |
| def self.included(base) |
| base.extend ClassMethods |
* | base.class_eval do |
* | alias_method_chain :save, :validation |
* | alias_method_chain :save!, :validation |
* | end |
| |
| # ... |
| |
| end |
When ActiveRecord::Base includes the Validations module, the marked lines reopen Base and call a method named alias_method_chain. Let me show you a quick example to explain what alias_method_chain does.
Suppose you have a module that defines a greet method. It might look like the following code.
| module Greetings |
| def greet |
| "hello" |
| end |
| end |
| |
| class MyClass |
| include Greetings |
| end |
| |
| MyClass.new.greet # => "hello" |
Now suppose you want to wrap optional functionality around greet—for example, you want your greetings to be a bit more enthusiastic. You can do that with a couple of Around Aliases ():
| class MyClass |
| include Greetings |
| |
| def greet_with_enthusiasm |
| "Hey, #{greet_without_enthusiasm}!" |
| end |
| |
| alias_method :greet_without_enthusiasm, :greet |
| alias_method :greet, :greet_with_enthusiasm |
| end |
| |
| MyClass.new.greet # => "Hey, hello!" |
I defined two new methods: greet_without_enthusiasm and greet_with_enthusiasm. The first method is just an alias of the original greet. The second method calls the first method and also wraps some happiness around it. I also aliased greet to the new enthusiastic method—so the callers of greet will get the enthusiastic behavior by default, unless they explicitly avoid it by calling greet_without_enthusiasm instead:
| MyClass.new.greet_without_enthusiasm # => "hello" |
To sum it all up, the original greet is now called greet_without_enthusiasm. If you want the enthusiastic behavior, you can call either greet_with_enthusiasm or greet, which are actually aliases of the same method.
This idea of wrapping a new feature around an existing method is common in Rails. In all cases, you end up with three methods that follow the naming conventions I just showed you: method, method_with_feature, and method_without_feature. The only the first two methods include the new feature.
Instead of duplicating these aliases all over the place, Rails provided a metaprogramming method that did it all for you. It was named Module#alias_method_chain, and it was part of the Active Support library. I’m saying “it was” rather than “it is” for reasons that will be clear soon—but if you look inside Active Support, you’ll find alias_method_chain is still there. Let’s look at it.
Here is the code of alias_method_chain:
| class Module |
| def alias_method_chain(target, feature) |
| # Strip out punctuation on predicates or bang methods since |
| # e.g. target?_without_feature is not a valid method name. |
| aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1 |
| yield(aliased_target, punctuation) if block_given? |
| |
| with_method = "#{aliased_target}_with_#{feature}#{punctuation}" |
| without_method = "#{aliased_target}_without_#{feature}#{punctuation}" |
| |
| alias_method without_method, target |
| alias_method target, with_method |
| |
| case |
| when public_method_defined?(without_method) |
| public target |
| when protected_method_defined?(without_method) |
| protected target |
| when private_method_defined?(without_method) |
| private target |
| end |
| end |
| end |
alias_method_chain takes the name of a target method and the name of an additional feature. From those two, it calculates the name of two new methods: target_without_feature and target_with_feature. Then it stores away the original target as target_without_feature, and it aliases target_with_feature to target (assuming that a method called target_with_feature is defined somewhere in the same module). Finally, the case switch sets the visibility of target_without_feature so that it’s the same visibility as the original target.
alias_method_chain also has a few more features, such as yielding to a block so that the caller can override the default naming, and dealing with methods that end with an exclamation or a question mark—but essentially, it just builds an Around Alias (). Let’s see how this mechanism was used in ActiveRecord::Validations.
Here is the code from the old version of ActiveRecord::Validations again:
| def self.included(base) |
| base.extend ClassMethods |
| # ... |
| base.class_eval do |
| alias_method_chain :save, :validation |
| alias_method_chain :save!, :validation |
| end |
| # ... |
| end |
These lines reopen the ActiveRecord::Base class and hack its save and save! methods to add validation. This aliasing ensures that you will get automatic validation whenever you save an object to the database. If you want to save without validating, you can call the aliased versions of the original method, now called save_without_validation.
For the entire scheme to work, the Validations module still needs to define two methods named save_with_validation and save_with_validation!:
| module ActiveRecord |
| module Validations |
| def save_with_validation(perform_validation = true) |
| if perform_validation && valid? || !perform_validation |
| save_without_validation |
| else |
| false |
| end |
| end |
| def save_with_validation! |
| if valid? |
| save_without_validation! |
| else |
| raise RecordInvalid.new(self) |
| end |
| end |
| def valid? |
| # ... |
The actual validation happens in the valid? method. Validation#save_with_validation returns false if the validation fails, or if the caller explicitly disables validation. Otherwise, it just calls the original save_without_validation. Validation#save_with_validation! raises an exception if the validation fails, and otherwise falls back to the original save_with_validation!.
This is how alias_method_chain was used around the times of Rails 2. Things have changed since then, as I will explain next.