Sorry this page looks weird. It was automatically migrated from my old blog, which had a different layout and different CSS.

Note To Self: Overriding A Method With A Mixin

You can add methods to a class by including a module:

class A
end

module M
  def answer
    42
  end
end

>> A.send :include, M
>> A.new.answer
=> 42

But you can’t override existing methods like this because included modules are inserted between the class and its superclass in the hierarchy. So methods defined in the class are found before their counterparts in included modules. You’ll end up overriding the superclass’s methods instead:

class A
  def answer
    'a'
  end
end

class B < A
  def answer
    'b' + super
  end
end

module M
  def answer
    'm'
  end
end

>> B.new.answer
=> 'ba'

>> B.send :include, M
>> B.new.answer
=> 'bm'

>> B.ancestors
=> [B, M, A, Object, Kernel]

Thanks to Robert Klemme for reminding me about this.

So how do you override methods with a mixin? Ara T Howard showed a solution that’s safe, generic and frankly rather stylish: store the original method in a variable and refer to it in the new method if you need to. Just be careful to preclude the variable from garbage collection.

class A
  def answer
    p 'a.42'
  end
end

class B
end

module M
  NoGC = []

  def self.included other
    other.module_eval do
      if (answer = instance_method 'answer' rescue false)
        NoGC.push answer
        supra = "ObjectSpace._id2ref(#{ answer.object_id }).bind(self).call(*a, &b)"
      end
      eval <<-code
        def answer *a, &b
          #{ supra }
          p "m.42"
        end
      code
    end
  end
end

A.send :include, M
B.send :include, M

>> A.new.answer
a.42
m.42

>> B.new.answer
m.42

The line building supra says get me the (unbound) Method object for the answer method in the class into which the module is being included, bind it back to the class and call it.

We redefine the original method in the eval block. If we wish to call the original pre-re-definition method, the original original method so to speak, we use supra in our new definition.

I think I would name the variable infra rather supra, but that may be because my brain hasn’t yet adapted to the new perspective.

In Ara’s words, “this allows you to both override and super up [not shown], in any combination, with a method injected late into a class hierarchy.”

Andrew Stewart • 17 January 2008 • RubyNotes To Self
You can reach me by email or on Twitter.