Yehuda Katz is a member of the Ember.js, Ruby on Rails and jQuery Core Teams; he spends his daytime hours at the startup he founded, Tilde Inc.. Yehuda is co-author of best-selling jQuery in Action and Rails 3 in Action. He spends most of his time hacking on open source—his main projects, like Thor, Handlebars and Janus—or traveling the world doing evangelism work. He can be found on Twitter as @wycats and on Github.

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 ;)

11 Responses to “Other ways to wrap a method”

The third method you describe is new to me, and was mindblowing in an elegant, Rubyish fashion. One more technique for wrapping methods is to unbind the pristine method, store it someplace, then redefine a method with the same name that runs callbacks, then binds the stored UnboundMethod to the given instance and calls it. Not nearly as elegant, but I believe it leads to some friendlier stacktraces than alias_monster_chain.

What about jruby?

I do like alias_method_chain. If you know how it works, it is easy to find the methods it uses. If you see a method name =~ /\w_with_\w/ or /\w_without_\w/, you can recognize what has been done. It is very transparent imho. I don’t like specifying your old method to be overridable, for the reason you mentioned. It sounds more natural for me to say to the overriding method that it’s overriding something. But it might be because I’m used to it. What about:

class Foo
def bar
1
end
override :bar do |old_bar|
old_bar 1
end
end

I have of course no idea about it’s implementation. Unfortunately ruby 1.8 doesn’t support blocks this way…

@teamon this technique can easily work on jruby

@iain the issue I have with amc is that it’s a very leaky abstraction. The goal here is to make an abstraction that is much less leaky so that the implementation doesn’t *matter*. If you go look at the implementation of alias_method you’ll see that it’s quite complex, but you don’t *have* to look at it because it works perfectly 100% of the time.

Crazy stuff Yehuda!

So, i don’t know the MRI internals well enough to judge whether this is a good idea from an implementational standpoint. I’ve got a comment and a question.

I’ve always wanted more powerful hooks in Ruby. I think it’d be cool to have the ability (before or after) a class or a method is defined or added to an object, to be able to manipulate that method, object or the host/parent in which the object is defined.

This sort of looks like a step in that direction (although not general purpose yeh?).

Further to that, how do you access prior versions of a method?

(Yehuda answered this question for me elsewhere. You’ve still got access to the ancestor chain of methods via ‘super’, so if you wanted any of the intermediate definitions of a method, you can get at ‘em, if you know the order in which they were added).

@Ted Han: Have a look at Extlib::Hook for general purpose before and after hooks. As Yahuda pointed out in another post the library lacks conditionals filters, but so does AMC. because hooks work on a per method basis there is really nothing to be done about filters, but I know conditional can be added. The only question in my mind is how expensive they will be.

maybe I am missing something, but why do you need to include module inside override
def override(&blk)
mod = Module.new do
extend TrackAddedMethods
end
mod.class_eval(&blk)
__move_to_module(*mod.__added_methods)
include mod
end

it looks like it is already included by __move_to_module… Am I missing something?

Nice writeup. I think there’s a typo when you first introduced the chainable method. ‘def chainable’ should be ‘def bar’. On a related note, I just released a gem to easily alias methods/classes when in irb: http://github.com/cldwalker/alias/tree/master

I like the idea of explicitly declaring a method as chainable.

My question, though, is how to you test/spec the wrapped method? With AMC, I usually stub or mock the aliased original method to make sure it is getting invoked as expected from within the wrapper. How do you do that when you are invoking it via super?

I just wanted to let you now, that I wrote a script (in ruby!) that does something like “the best of all worlds ( without a caveat)”:

http://github.com/rkh/chainable

Leave a Reply

Archives

Categories

Meta