How To Replace alias_method_chain

Wendy
Rails

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.