Ruby on Rails: alias_method_chain, what exactly does it do?
I've tried reading through various blog posts that attempt to explain alias_method_chain and the reasons to use it and not use it. In particular, I took heed to:
http://weblog.rubyonrails.org/2006/4/26/new-in-rails-module-alias_method_chain
and
http://yehudakatz.com/2009/03/06/alias_method_chain-in-models/
I still do not see any practical use for alias_method_chain. Would anyone be able to explain a few things.
1 - is it still used at all?
2 - when would you use alias_method_chain and why?
Solution 1:
1 - is it still used at all?
Apparently yes, alias_method_chain()
is still used in Rails (as of version 3.0.0).
2 - when would you use alias_method_chain and why?
(Note: the following is largely based on the discussion of alias_method_chain()
in Metaprogramming Ruby by Paolo Perrotta, which is an excellent book that you should get your hands on.)
Let's start with a basic example:
class Klass
def salute
puts "Aloha!"
end
end
Klass.new.salute # => Aloha!
Now suppose that we want to surround Klass#salute()
with logging behavior. We can do that what Perrotta calls an around alias:
class Klass
def salute_with_log
puts "Calling method..."
salute_without_log
puts "...Method called"
end
alias_method :salute_without_log, :salute
alias_method :salute, :salute_with_log
end
Klass.new.salute
# Prints the following:
# Calling method...
# Aloha!
# ...Method called
We defined a new method called salute_with_log()
and aliased it to salute()
. The code that used to call salute()
still works, but it gets the new logging behavior as well. We also defined an alias to the original salute()
, so we can still salute without logging:
Klass.new.salute_without_log # => Aloha!
So, salute()
is now called salute_without_log()
. If we want logging, we can call either salute_with_log()
or salute()
, which are aliases of the same method. Confused? Good!
According to Perrotta, this kind of around alias is very common in Rails:
Look at another example of Rails solving a problem its own way. A few versions ago, the Rails code contained many instances of the same idiom: an Around Alias (155) was used to add a feature to a method, and the old version of the method was renamed to something like
method_without_feature()
. Apart from the method names, which changed every time, the code that did this was always the same, duplicated all over the place. In most languages, you cannot avoid that kind of duplication. In Ruby, you can sprinkle some metaprogramming magic over your pattern and extract it into its own method... and thus was bornalias_method_chain()
.
In other words, you provide the original method, foo()
, and the enhanced method, foo_with_feature()
, and you end up with three methods: foo()
, foo_with_feature()
, and foo_without_feature()
. The first two include the feature, while the third doesn't. Instead of duplicating these aliases all around, alias_method_chain()
provided by ActiveSupport does all the aliasing for you.
Solution 2:
alias_method_chain
has been deprecated in Rails 5 in favour of Module#prepend
.
Pull request: https://github.com/rails/rails/pull/19434
Changelog: https://github.com/rails/rails/blob/b292b76c2dd0f04fb090d49b90716a0e6037b41a/guides/source/5_0_release_notes.md#deprecations-4
Solution 3:
I'm not sure if it's gone out of style with Rails 3 or not, but it is still actively used in versions before that.
You use it to inject some functionality before (or after) a method is called, without modifying any place that calls that method. See this example:
module SwitchableSmtp
module InstanceMethods
def deliver_with_switchable_smtp!(mail = @mail)
unless logger.nil?
logger.info "Switching SMTP server to: #{custom_smtp.inspect}"
end
ActionMailer::Base.smtp_settings = custom_smtp unless custom_smtp.nil?
deliver_without_switchable_smtp!(mail = @mail)
end
end
def self.included(receiver)
receiver.send :include, InstanceMethods
receiver.class_eval do
alias_method_chain :deliver!, :switchable_smtp
end
end
end
That's an addition to ActionMailer to allow swapping out of the SMTP settings on each call to deliver!
. By calling alias_method_chain
you are able to define a method deliver_with_switchable_smtp!
in which you do your custom stuff, and call deliver_without_switchable_smtp!
from there when you're done.
alias_method_chain
aliases the old deliver!
to your new custom method, so the rest of your app doesn't even know deliver!
now does your custom stuff too.