Other ways to wrap a method
This is something of a follow-up to Bruce Williams' wrap-up of "wrapping a method" approaches. Bruce covered two approaches:
alias_method_chain: rename the original method, then copy the new method over it and call the old one.
class Foo
def bar
1
end
alias_method :original_bar, :bar
def bar
original_bar + 1
end
end
super and extend: override #new and extend the new object's metaclass with the method.
class Foo
def bar
1
end
module Incrementor
def bar
super + 1
end
end
def self.new
super.extend Incrementor
end
end
I wanted to cover two other options, which come with more serious caveats than Bruce's approaches, but which have their benefits.
explicitly declaring a method as chainable: if you are in control of the code you want to extend, make it chainable from the beginning
class Foo
chainable = Module.new do
def bar
1
end
end
include chainable
def chainable
super + 1
end
end
What's going on above is that instead of declaring the method on the class itself, we're sticking it into an (anonymous) module, then including the module into Foo. As a result, we can now define a method on Foo itself, and super to the module we included. We can even wrap up the idiom pretty simply:
class Module
def overridable(&blk)
mod = Module.new(&blk)
include mod
end
end
class Foo
overridable do
def bar
1
end
end
def bar
super + 1
end
end
I like this approach a lot, but it does require that you have control over the original code. In some cases (for instance, Rails chaining its own code via modules) that is the case. This approach, in my opinion, is superior to alias_method_chain because it does not mutate method names, so it eliminates the confusion of looking for a method from a stack trace on a certain line and finding a method with a different name. Also, having to explicitly declare a method chainable provides a cue to the reader of the code that the method is not complete, and that there might be other components elsewhere.
the best of all worlds (with a caveat): the best option would allow you the benefits of the approach immediately above without requiring that you declare the method chainable (so you could add advice to other peoples' code). The solution, unfortunately, requires a (rather crude) C extension I whipped up.
There are a few things going on above. The interesting part is the little bit of C code. This code lives on any Module. It grabs a method from the module's method table, and inserts it (with the same name) on any other Module. The TrackAddedMethods module can be extended onto any module, and it will remember all the methods that were added (using a method added hook).
The override
method uses the same technique as the chainable method above to create a new module with the methods inside of it. Only this time, as the methods get added, we track them. Each method that is added to the module is then removed from the class itself and moved up (via the C extension) to a module, which is then included into the base class. At this point, the base class doesn't have the method on it anymore, but the original method is included into the base class via a module.
At this point, we're at the same place as we would have been using the first technique I described above, so we simply include the module that was created from the override() method and voila: you can tweak someone else's methods without needing to rename them.
I hope the forgoing didn't blow anyone's minds. It blew mine the first time I started playing with it ;)