How To Replace alias_method_chain
alias_method_chain
was deprecated in Rails 5.0 and removed in 5.1, in favor of
Module#prepend
. When upgrading a Rails application or gem that uses this
method, a bit of restructuring is required.
Say you have:
class Foo
def bar
"foo#bar"
end
end
Later, you need to change this behavior and have decided that
alias_method_chain
is the way to do it, perhaps because you read this helpful
blog post by Ernie
Miller, and you
did, indeed, need to override a method on a class which you have no control over.
Or perhaps you’ve inherited a project where you find the following code already
exists, and it still meets a use case where it cannot be refactored to use simple
inheritance.
class Foo
def bar_with_extra_stuff
"#{bar_without_extra_stuff} with extra stuff"
end
alias_method_chain :bar, :extra_stuff
end
In Rails 5.1, we can override Foo#bar
with super
by using Module#prepend
, which
makes the prepended module’s methods act as if they inherit from the class,
instead of stopping the lookup chain at methods defined in the class.
Example:
module BarWithExtraStuff
def bar
"#{super} with extra stuff"
end
end
class Foo
prepend BarWithExtraStuff
def bar
"Foo#bar"
end
end
Now we can call Foo#bar
and get the results of #bar
as called in the
BarWithExtraStuff
module.
alias_method_chain
would have originally created the method
bar_without_extra_stuff
, and the method bar_with_extra_stuff
would have been
defined in the code. Module#prepend
does not give us access to the old method
- so this is something to keep in mind.
Sometimes code is messy, though, and there is not an immediate solution using
Module#prepend
, as was the case in a very large ten-year-old project that my team
and I upgraded from Rails 3.x to 5.1 over the course of the last year or so.
alias_method_chain
was used and buried in metaprogramming, and replacing it
with the suggested solution would have required the creation of more
metaprogrammed modules that did not make the code easier to read or maintain.
In (hopefully temporary) cases such as this, it’s useful to remember the origins
of alias_method_chain
. It was created to abstract a common pattern used in the
Rails code base itself:
class Foo
def bar
"Foo#bar"
end
def bar_with_extra_stuff
"#{bar_without_extra_stuff} with extra stuff"
end
alias_method :foo_without_extra_stuff, :foo
alias_method :foo, :foo_with_extra_stuff
end
This is still perfectly legal code that can be used in a pinch, and will maintain the original behavior exactly.