I thought that including a module as a mixin in a class "added the functions" to the class.
I do not understand why this does not work as expected:
module A
def blah
super if defined?(super)
puts "hello, world!"
end
end
class X
include A
end
class Y < X
include A
end
y = Y.new
y.blah
I was expecting "y" to call its super blah() (since its included in class X?) but instead i got:
test.rb:3:in blah: super: no superclass method `blah'
You're running into nuances of Ruby's object hierarchy and how method lookups interact with included modules.
When you invoke a method on a object, Ruby walks over the
ancestorslist for the object's class, looking for an ancestor class or module that responds to that method. When you invokesuperin that method, you're effectively continuing your walking up the tree ofancestors, looking for the next object that responds to the same method name.The ancestor tree for your
XandYclasses look like this:The problem is that
includeing the module a second time, in a child class, does not inject a second copy of the module in the ancestors chain.Effectively what is happening is when you invoke
Y.new.blah, Ruby begins looking for a class that responds toblah. It walks pastY, andX, and lands onAwhich introduces theblahmethod. WhenA#blahinvokessuper, the "pointer" into your ancestor list is already pointing atA, and Ruby resumes looking from that point for another object responding toblah, starting withObject,Kernel, and thenBaseObject. None of these classes have ablahmethod, so yoursuperinvocation fails.A similar thing happens if a module
Aincludes a moduleB, and then a class includes both moduleAandB. TheBmodule is not included twice:Note that it's
C, B, A, notC, A, B, A.The intent would seem to be to allow you to safely invoke
superinside any ofA's methods without worrying about how consuming class hierarchies may inadvertently includeAtwice.There are a few experiments that demonstrate different aspects of this behavior. The first is adding a
blahmethod to Object, which allows thesupercall to pass:The second experiment is to use two modules,
BaseAandA, which does cause the modules to be inserted twice, correctly, in theancestorschain:A third experiement uses
prepend, instead ofinclude, which places the module in front of the object in theancestorshierarchy and interestingly does insert a duplicate copy of the module. This allows us to reach the point where effectivelyY::blahinvokesX::blah, which fails becauseObject::blahdoes not exist: