Wrapping a Method in Ruby

Let’s say you have a Ruby class with a method you’d like to wrap—for debugging or performance timing—and, since you don’t control where the class is instantiated (think overriding a method in Rails’ ActionPack), just creating a subclass and using super isn’t going to work.

Let’s take a look at two mixin patterns; one a ubiquitous naming hack and one a bit of esoteric Ruby inheritance trickery.

First, let’s set the scene. Let’s say we have a method, Widget#render_on:

class Widget
  # Wrap this one
  def render_on(document, options = {})
    # ...
  end
  # ...
end

What we want is for each wrapper we write to call a method, which we’ll call snapshot, that will output some information about the method invocation. Don’t worry about the implementation too much here; it just takes a block and times the yield:

def snapshot
  start = Time.now
  result = yield
  # You could store these values somewhere
  # for averaging, track the caller, etc
  puts Time.now - start
end

So, we’re going to do this two ways…

Technique #1: alias_method_chain

ActiveSupport makes this easy. Though arguably a bit of a messy naming hack, it does the job and is a bit self-documenting, forcing you to name a feature. If you’re using Rails, this is happening all over the place.

module AliasMethodChainWrapper

  def self.included(base)
    base.send(:include, InstanceMethods)
    base.alias_method_chain :render_on, :wrapping
  end

  module InstanceMethods

    def render_on_with_wrapping(*args, &block)
      snapshot { render_on_without_wrapping(*args, &block) }
    end

  end

end

Now, let’s mix this baby in.

class Widget
  include AliasMethodChainWrapper
end

What happens when we do this?

In the included hook, AliasMethodChainWrapper::InstanceMethods is included into Widget (meaning Widget instances now have access to render_on_with_wrapping)

alias_method_chain aliases the original render_on to render_on_without_wrapping and aliases the just-added render_on_with_wrapping to render_on, overriding the original implementation.

Here’s an illustration of what the object hierarchy looks like, and how the method invocation works in this technique.

This allows you to call the original implementation of a method from your wrapper code, if desired (we do, invoking it inside a block that’s passed to snapshot for reporting).

You can see the API documentation for alias_method_chain here for more information. There’s plenty of examples of this common pattern in Rails plugins and the framework itself.

On we go to purer Ruby pastures.

Technique #2: super and extend

So now let’s talk a bit about a technique that relies on Ruby’s extend and super capabilities to simplify things a bit and get rid of the need for explicit naming. Here’s the wrapper in question:

module SuperExtendWrapper

  def new(*args, &block)
    super.extend Wrapping
  end

  module Wrapping

    def render_on(*args, &block)
      snapshot { super }
    end

  end

end

Okay, that’s a fair bit shorter, isn’t it? Deceptively simple. Now, let’s hook it into our Widget…

Widget.extend SuperExtendWrapper

So, we use Object#extend this time, which, according to ri, “Adds to the object the instance methods from each module given as a parameter.” How this happens is a bit tricky, and involves shoehorning the module into the instance’s metaclass — singleton class, if you prefer — ancestry. Let’s see how this happens with our wrapper.

Widget is extended with SuperExtendWrapper (which defines the new method). We can see that SuperExtendWrapper is now below Widget in the class metaclass using irb:

>> (class << Widget; self; end).ancestors
=> [SuperExtendWrapper, Class, Module, Object, Kernel]

What this means is we can use super to call the vanilla Class#new. Handy, since we need it to get an instance! We then immediately extend the instance with another module, SuperExtendWrapper::Wrapping, our real payload.

As you might guess, extending the Widget instance with SuperExtendWrapper::Wrapping gave us a similar hierarchy in the instance’s metaclass. Let’s take a peek:

>> widget = Widget.new >> (class << widget; self; end).ancestors
=> [SuperExtendWrapper::Wrapping, Widget, Object, Kernel]

This makes invoking the vanilla Widget#render_on just as easy as Class#new; it’s a mere super away. No naming hacks required. Here’s an illustration showing how the method invocation works using the super-and-extend technique.

Keep in mind we’ve only covered instance methods here; things get substantially more hairy using the super-and-extend approach to wrap class methods — whereas alias_method_chain tackles the problem fairly handily.

Performance Note

You may have noticed that the alias_method_chain magic only needs to be executed one time, whereas the super-and-extend trick needs to extend every instance.

The performance characteristics turn out to be a bit tricky here, considering—at least in MRI—while extend adds overhead, method invocation via super is a good bit snappier than calling arbitrary methods (ie, alias_method_chain’s with and without chaining).

What this means is that if you’re keeping an instance around and calling a wrapped method multiple times, it’s very easy to get better performance from super-and-extend than alias_method_chain, but if you’re instantiating a lot of single-use instances, results will be in alias_method_chain’s favor. Obviously we’re talking tiny performance differences stacking up in either case.

Further Reading

The vagaries of extend are a common topic of conversation; you may find the following interesting reading:

Update: See my follow-up to this article, More on super-and-extend.