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
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_chaintwice 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 your
requirestatements 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_chainin favor of module inheritance.
So, why is it still used?
You can replace most
alias_method_chains 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:
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:
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 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.