4

I was reading another question with an answer that mentions using the Module#const_get instance method to find a class in a module. For example:

module M
  class C
  end
end

p M.const_get 'C'
#=> M::C

I was curious about the const_get method so I used ri and found:

ri Module#const_get
...
This method will recursively look up constant names if a namespaced
class name is provided.  For example:

  module Foo; class Bar; end end
  Object.const_get 'Foo::Bar'
...

It seems like Object::const_get is a singleton method. Using it in our context works:

module M
  class C
  end
end

p Object.const_get 'M::C'
#=> M::C

But there's nothing documented about that singleton method:

ri Object::const_get
Nothing known about Object::const_get
ri Object.const_get
Nothing known about Object.const_get

This confused me because I know a Module is an Object but an Object is not a Module:

Module.ancestors
#=> [Module, Object, Kernel, BasicObject]
Object.ancestors
#=> [Object, Kernel, BasicObject]

Except then I used the Object#is_a? instance method to check and saw that I was wrong about that:

Module.is_a? Object
#=> true
Object.is_a? Module
#=> true

What started as an innocent ri query has led me to be confused about the entire Ruby object model.

  • Why does Object.is_a? Module #=> true if Module is not in Objects ancestor chain?
  • How does Object know about the const_get method?
mbigras
  • 7,664
  • 11
  • 50
  • 111

3 Answers3

6

This is an artifact of the ill-understood separation between an object's class and an object's singleton class, a sort of shadow class that each class uses for things like this.

You can access this easily in Ruby 2.5+ with the singleton_class method:

Object.singleton_class.ancestors
# => [#<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]

Where Module makes an appearance here, so that's how those methods get mixed in and are callable via Object.

Object itself has a comparatively dull inheritance chain:

Object.ancestors
#=> [Object, Kernel, BasicObject]

Every object in Ruby has a class, even Class is an Object, which also has an associated Class.

mbigras
  • 7,664
  • 11
  • 50
  • 111
tadman
  • 208,517
  • 23
  • 234
  • 262
  • Ah! I think I was looking at the wrong ancestor chain! Looking at the ancestor chain for `Object` mislead me because `Object` is a class, so the ancestor chain will show which methods are available to instances of that class right? But `Object` also has a singleton class (like all objects), and checking that ancestor chain will show which methods are available to the actual object `Object`. – mbigras Apr 17 '18 at 00:09
  • 1
    I like how you used `Object#singleton_class` that's what helped it click for me! Are you able to add a mention that about the two different ancestor chains, one for `Object` and one for `Object.new`? Checking `Object.singleton_class.ancestors` and `Object.new.singleton_class.ancestors` answers the question about `is_a?` – mbigras Apr 17 '18 at 00:13
1

I think your confusion stems from looking at Object from two directions at once:

  1. Object is a class so Object.ancestors can be used to look at the inheritance hierarchy. This tells you that Object < Kernel is true and Object < Module is false.
  2. Classes in Ruby are also objects, specifically they're instances of the Class class. This tells you that Object.is_a? Class and Object.is_a? Module in the same way that 'pancakes'.is_a? String. And that Object.const_get is a method call in the same way that 'pancakes'.upcase is a method call.

You can think of some_obj.is_a? SomeClass as a shorter way of saying some_obj.class.ancestors.include? SomeClass.

Answering your specific questions:

  1. Why does Object.is_a? Module #=> true if Module is not in Object's ancestor chain?

    Because is_a? and ancestors are looking at different things.

  2. How does Object know about the const_get method?

    Because Object is an instance of the Class class and Class includes Module in its ancestors. Similarly to how 'pancakes' is an instance of the String class which has Kernel in its ancestors so 'pancakes' has an object_id method.

Community
  • 1
  • 1
mu is too short
  • 426,620
  • 70
  • 833
  • 800
0

Object.is_a? Module #=> true

is_a? is asking whether Object is an instance (or specialised instance) of Module. Class is a subclass of Module so all instances of Class are really just specialised instances of Module. Since Object is an instance of Class, it follows that Object is also an instance of Module.

Module#const_get

const_get is an instance method defined within the Module class. Since Object is an instance of Module (for reasons discussed above), it has access to Module#const_get.

Community
  • 1
  • 1
Sagar Pandya
  • 9,323
  • 2
  • 24
  • 35