9

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 class 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 class 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 class method of Car"
      super
    end
  end
end

puts Car.banner

# I am a class method of Car
# I am a class method of Vehicle
# I am a regular method of Automobile
saihgala
  • 5,724
  • 3
  • 34
  • 31

2 Answers2

9

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"
Patrick Oscity
  • 53,604
  • 17
  • 144
  • 168
  • Firstly thanks for your response. I get it that when I include a module its eigenclass methods are ignored and only its instance methods are exposed to the class. What I am actually trying to understand is the rationale behind this behaviour? – saihgala Nov 07 '12 at 13:58
  • It's simply consistent behavior. But i honestly don't know why you can't `include` class methods. – Patrick Oscity Nov 07 '12 at 14:06
  • maybe we're looking at it from different vantage points but when you extend a class its instance as well as eigenclass methods are exposed to child class (Car in this example has access to both `Vehicle.banner` as well as `Vehicle.new.banner`). And thus withholding Automobile's singleton `banner` method from Vehicle seems inconsistent to me. – saihgala Nov 07 '12 at 14:12
  • Yes, but inheritance is not the same! `include` by definition only includes instance methods. Class inheritance does both. Remember, that modules are nothing more than a mechanism to group other code together. – Patrick Oscity Nov 07 '12 at 14:17
  • Searching through archives I found in Matz words - `Mix-in is used for several purposes and some of them can be hindered by inheriting class methods, for example, I don't want to spill internal methods when including the Math module. I am not against for some kind of inclusion that inherits class methods as well, but it should be separated from the current #include behavior.` – saihgala Nov 08 '12 at 06:24
  • Got it: `Class` is a `Module` and `Class` does more stuff than `Module` – William Entriken Feb 17 '16 at 16:08
2

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.

Patrick Oscity
  • 53,604
  • 17
  • 144
  • 168
Need4Steed
  • 2,170
  • 3
  • 22
  • 30
  • I like your `eigenclass` definition on `Object`. And you should change `ancestor` to `ancestors`. – saihgala Nov 08 '12 at 06:24
  • It is helpful for inspection to add an eigenclass method to Object like above. Surprised this has not found its way into Ruby by default – fontno Jul 06 '13 at 23:43