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.”
Posted in Notes To Self, Ruby

0 Comments
Jump to comment form