I understand the regular method lookup path i.e. class, superclass/module, all the way up to BasicObject
. I thought it was true for singleton version of the chain also but doesn't seem the case when you mixin a module in the meta-chain. I'd appreciate if someone can explain why in the following example Automobile
module's banner
method is called instead of its singleton version when I have included this module in Vehicle's eigenclass.
module Automobile
def banner
"I am a regular method of Automobile"
end
class << self
def banner
"I am a singleton method of Automobile"
end
end
end
class Vehicle
def banner
"I am an instance method of Vehicle"
end
class << self
include Automobile
def banner
puts "I am a singleton method of Vehicle"
super
end
end
end
class Car < Vehicle
def banner
"I am an instance method of Car"
end
class << self
def banner
puts "I am a singleton method of Car"
super
end
end
end
puts Car.banner
# I am a singleton method of Car
# I am a singleton method of Vehicle
# I am a regular method of Automobile
First of all, include
does not include eigenclass methods as you might expect. Consider:
module Foo
class << self
def do_something
puts "Foo's eigenclass method does something"
end
end
end
module Bar
include Foo
end
puts Bar.do_something
# undefined method `do_something' for Bar:Module (NoMethodError)
Note that this is consistent with the behavior of classically defined class methods:
module Foo
def self.do_something
puts "Foo's class method does something"
end
end
module Bar
include Foo
end
puts Bar.do_something
# undefined method `do_something' for Bar:Module (NoMethodError)
A common idiom is to define the class methods in a submodule and then trigger a call to extend
when the module is included:
module Foo
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def do_something
puts "Foo::ClassMethod's instance method does something"
end
end
end
module Bar
include Foo
end
puts Bar.do_something
# Foo::ClassMethod's instance method does something
The second thing to note is, that you are really including the instance methods of Automobile
into the eigenclass of Vehicle
, thus the instance methods of Automobile
turn into (eigen)class methods of Vehicle
.
Your Car
class basically has nothing to do with all this. The only thing to note here is, that class inheritance also makes class methods available, whereas include
does not. Example:
class Foo
def self.do_something
puts "Foo's class method does something"
end
end
class Bar < Foo
end
puts Bar.do_something
# "Foo's class method does something"
First of all, a class is an object, just like the other objects it also has its own superclass;
second of all, Eigenclass itself is a normal class, only anonymous and sorta invisible;
third, the eigenclass's superclass of the derived class is the eigenclass of the base class;
Fourth, include
includes instance methods (not singleton methods) of the included module, make them instance methods of the receiver class object.
There're two parallel inheritance chains in your example
Car < Vehicle < ...
Car's eigenclass < Vehicle's eigenclass < Automobile < ...
Do the following test on irb:
class Object
def eigenclass
class << self
self
end
end
end
Car.ancestors # => [Car, Vehicle, Object, Kernel, BasicObject]
Car.eigenclass.ancestors # => [Automobile, Class, Module, Object, Kernel, BasicObject]
Vehicle.eigenclass.ancestors # => [Automobile, Class, Module, Object, Kernel, BasicObject]
Car.eigenclass.superclass.equal? Vehicle.eigenclass # => true
You see, Automobile
is in the eigenclass inheritance chain. But regretably, the ancestor
method doesn't return invisible eigenclasses, nonetheless they are actually in the second chain.