The Rails 4.2 announcement had some interesting news about the upcoming Rails 5: It’s probably going to require Ruby 2.2. Which will make it the first Rails version to take advantage of all the good stuff from Ruby 2.
The post mentioned garbage collected symbols and keyword arguments. But to me, one of the most interesting Ruby 2 features is Module#prepend.
The good (and bad) of alias_method_chain
When I first learned Rails, alias_method_chain
totally blew my mind. It really showed off how flexible Ruby could be.
With just a single line of code, you could completely change the way a method worked. You no longer needed to hack around libraries to add the code you wanted, you could just add it on the fly. alias_method_chain
led to my first patches to gems, which led to my first pull requests, which led to my first open source contributions.
But just like monkey patching, alias_method_chain
got overused, and its problems started to become obvious:
- The method names it generates are confusing, which makes errors hard to find and debug. For example:
If you had an error in Person#greeting
, the backtrace would tell you that an error actually happened in Person#greeting_without_excitement
. But where is that method even defined? I don’t see it anywhere. How do you know which greeting
method is the one with the bug in it? And the method names get even more confusing the more you chain.
-
If you call
alias_method_chain
twice with the same parameters on the same class, you could cause a stack overflow. (Can you see why?) This doesn’t normally happen, as long as yourrequire
statements are consistent about which paths they use. But it’s super annoying if you frequently paste code into a Rails console. -
And the rest of the things described by Yehuda Katz’s blog post. This post convinced a lot of Rails devs to start abandoning
alias_method_chain
in favor of module inheritance.
So, why is it still used?
You can replace most alias_method_chain
s by overriding those methods in a module, and including those modules into your child classes. But that only works if you want to override your superclass, not your class itself. That is:
If you ran ChildClass.new.log
, you’d see:
if you tried to use modules instead of alias_method_chain
, you could get the output to be:
But you cannot match the original output without changing the log
method in ChildClass
. Ruby inheritance doesn’t work that way. Well, it didn’t.
What changed in Ruby 2.0?
Until Ruby 2.0, there was no way to add code below a class, only above it. But with prepend
, you can override a method in a class with a method from a module, and still access the class’s implementation with super
. So, using our last example, we could get the original output with:
Perfect.
If prepend
is still hard to wrap your head around, think of it as doing something like this:
Except it won’t mess with your class names, and it affects objects that already exist.
(Yes, you can reassign class names in Ruby. No, it’s probably not a great idea.)
What does this mean for Rails?
So, the last excuse for using alias_method_chain
is gone in Ruby 2.0. We could take one of the few remaining examples of alias_method_chain
in Rails:
and replace it with a module, instead:
It’s cleaner, Range#each
doesn’t get renamed, and ensure_iteration_allowed
isn’t monkey-patched in.
Use inheritance, not patches
Ruby gives you a ton of flexibility, and that’s one of the reasons I love it. But it also has a powerful object model. So when you want to inject your own code, try leaning on modules and inheritance before you hack it in. Your code will be much easier to understand and debug, and you’ll avoid some of the harder-to-detect side effects of something like alias_method_chain
.
alias_method_chain
was one of the coolest methods I was introduced to in Rails. But its days are numbered. We’ve outgrown it. And I’m not going to miss it when it’s gone.