Ruby Object Model - ancestors of a class - ruby

I'm studying the Ruby Object Model from the book "Metaprogramming Ruby" and I understand the notion of how classes are Objects as well.
class A
end
A.ancestors # => [A, Object, Kernel, BasicObject]
A.class.ancestors # => [Class, Module, Object, Kernel, BasicObject]
What I'm confused about is that when I initially tried A.ancestors in irb, I expected the results that I got in A.class.ancestors - my thought process was: since A is a class, and a class is an instance of class Class, it's ancestor is Class. But Class doesn't seem to be an ancestor of A.
Would someone be able to clear up my confusion here?

The class A is an instance of Class, and you can see that via A.class
The class of an instance of A is A, and you access that via a = A.new; a.class
The method ancestors is showing the class hierarchy that an object of that class has (or would have) as its inheritance.
There are two parallel class hierarchy models going on in your example, and they only impinge on each other because Ruby represents its classes as objects for you to inspect and modify.
There is no fundamental reason to need A.class.ancestors and A.ancestors to intersect at all - except Ruby also has a deep class model with simple roots, so in practice that is what you'll see.
In fact I couldn't find any counter-example, even nil does this:
NilClass.ancestors
=> [NilClass, Object, Kernel, BasicObject]
NilClass.class.ancestors
=> [Class, Module, Object, Kernel, BasicObject]
This one is more enlightening though:
BasicObject.ancestors
=> [BasicObject]
BasicObject.class.ancestors
=> [Class, Module, Object, Kernel, BasicObject]

Related

How does Object know about the const_get method?

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?
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.
I think your confusion stems from looking at Object from two directions at once:
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.
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:
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.
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.
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.

Ruby: Not able to understand why class method is accessible in child class

As per Ruby method lookup law whenever we invoke any method on an object then ruby finds the method using formula object.class.ancestors. If this is true then I should not be able to access parent method defined in parent class using Child class constant as Child.parent because the ancestors of Child class are [Class, Module, Object, Kernel, BasicObject]. But I can access it. Can anyone tell me why is so?
class Parent
def self.parent
puts "i am parent"
end
end
class Child < Parent
end
Child.parent # i am parent
my jruby version is jruby 1.7.16 (1.9.3p392) 2014-09-25 575b395 on Java HotSpot(TM) 64-Bit Server VM 1.8.0_20-b26 +jit [Windows 8.1-amd64]
Your understanding is not entirely correct. object.class.ancestors does not give you the full list of classes/modules where methods will be searched for. Here is a counter example:
foo = Object.new
def foo.bar
puts "I'm inside the singleton class"
end
foo.class.ancestors # => [Object, Kernel, BasicObject]
foo.bar # "I'm inside the singleton class"
Instead you have to start with the singleton class of the object:
foo.singleton_class.ancestors # => [#<Class:#<Object:0x007fa92c33c610>>, Object, Kernel, BasicObject]
foo.singleton_class.instance_methods.include?(:bar) # => true
Knowing that when invoking object.method_name, #method_name is searched for somewhere in
object.singleton_class.ancestors
and that Ruby classes are regular objects, it only stands that with Child.parent, #parent will be looked for somewhere in
Child.singleton_class.ancestors
The magic is that class methods in Ruby aren't special in any way. They are just defined in the singleton class of the class. In your example:
Child.singleton_class.ancestors # => [#<Class:Child>, #<Class:Parent>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
As you can see, the Child's singleton class ancestors chain includes the Parent's singleton class. That is why when you invoke Child.parent you actually call Parent.parent.
the parents of Child class are [Class, Module, Object, Kernel, BasicObject]
No, they're not:
Child.ancestors
#=> [Child, Parent, Object, Kernel, BasicObject]
You can access Child.parent because Child inherits from Parent.
In order to gain a full picture of what methods are available to instances of a class, however, you need to look at the singleton_class:
Child.singleton_class.ancestors
#=> [#<Class:Child>, #<Class:Parent>, #<Class:Object>,
#<Class:BasicObject>, Class, Module, Object,
Kernel, BasicObject]

Why instances don't have 'Class' as a parent in Ruby?

p 'a'.class.ancestors #=> [String, Comparable, Object, Kernel, BasicObject]
p String.class.ancestors #=> [Class, Module, Object, Kernel, BasicObject]
Suppose I call a method on 'a'. It first goes and looks for that object in the String class, if it doesn't find it there, it looks in Comparable etc.
Now if I call a method on the Class class, it first goes to 'Class', then 'Module' etc.
What I don't understand is this. Why, when I call a method on an instance of a string like 'a', it doesn't look-up the methods in 'Class'? Why doesn't 'a' have Class in its ancestors list?
Because 'a' is not a class, 'a' is a string, that's why 'a' is an instance of the class String. String is a class, so it's an instance of the class Class.
Keep this in mind, Class is not and ancestor of String. Class is the class of String.
When you call a method on 'a', ruby will try to find that method in 'a''s class String and its ancestors, none of which is Class
More Discussion
Normally speaking, when we are not having such abstract discussion, you'd say 'a' is a String, or String is a Class. But that might lead to confusion in the current context.
This is because the is a relationship in current context could mean at least two different thing. x is a Y could either mean that object x is an instance of class Y, or it could mean that class x inherits from class Y. (of course naming conventions would suggest that x is an object and Y is a class.. but those are not solid enough)
This vagueness of the is a relationship is the root of the confusion here. Because in ruby, all classes are also objects, objects of type Class. To make the whole situation both confusing and elegant at the same time, Class is also an object! ...of type Class!!
[7] pry(main)> 'a'.ancestors
NoMethodError: undefined method `ancestors' for "a":String
from (pry):7:in `__pry__'
[8] pry(main)> 'a'.class.ancestors
=> [String, Comparable, Object, PP::ObjectMixin, Kernel, BasicObject]
[9] pry(main)> String.ancestors
=> [String, Comparable, Object, PP::ObjectMixin, Kernel, BasicObject]
[10] pry(main)> String.class.ancestors
=> [Class, Module, Object, PP::ObjectMixin, Kernel, BasicObject]
[11] pry(main)> Class.ancestors
=> [Class, Module, Object, PP::ObjectMixin, Kernel, BasicObject]
[12] pry(main)> Class.class.ancestors
=> [Class, Module, Object, PP::ObjectMixin, Kernel, BasicObject]
So if we were to simply talk in terms of is a relationship. String is a Class and a String is a Comparable. But String doesn't inherit from Class, it's an instance of Class. And String is not an instance of Comparable, it inherits from Comparable.
Now is a good time to refresh the meaning of the inherits from relationship. String inherits from Comparable, that means that all objects of type String will also inherit behavior from class Comparable. And since String does not inherit from Class, object instances of type String won't inherit any behavior from class Class.
Back to "is a" relationship. String is an Object. Now this applies to both relationships. String certainly inherits from Object. But also Class, what String is an instance of, inherits from Object. And thus String also inherits the behavior of class object. So when we say String is an Object, it could mean either of the things. Although, if we were talking about inheritance, we'd probably say "a String is an Object", meaning that instances of class String are also objects. While saying "String is an Object" (notice I dropped the article 'a') we'd mean that the class String itself is an object.
I just realized something else. Remember how we declare static methods in Ruby? Ruby on rails - Static method
You say...
class X
def self.static_method
...
end
end
If you think about it, by using the keyword self, we are referring the object nature of X. So when later you call X.static_method, you are accessing X as an object and referring to an instance method associated with object X.
Hope I've not confused you guys too much.

Ruby ancestors method for singleton class objects

The Ruby documentation has a simple definition for the Module#ancestors method that talks about what ancestors returns when it's called on modules, but does not say anything about when ancestors is called on classes.
Returns a list of modules included in mod (including mod itself).
I'm trying to decipher the purpose of the ancestors method when it's called on classes, especially singleton class object. Here's an example:
module InstanceMethods; end
module ClassMethods; end
class A; end
class B < A
include InstanceMethods
extend ClassMethods
end
B.ancestors # => [B, InstanceMethods, A, Object, Kernel, BasicObject]
B.singleton_class.ancestors # => [ClassMethods, Class, Module, Object, Kernel, BasicObject]
Questions:
Why is the receiver not included in the ancestors list when the method is called on singleton_class objects? I would have expected B.singleton_class.ancestors to equal [B, ClassMethods, Class, Module, Object, Kernel, BasicObject]
I think the ancestor path for B's singleton class should be more like this (this isn't perfect because it shouldn't be using the singleton class of modules):
B.singleton_class.ancestors.map(&:singleton_class) # => [#<Class:ClassMethods>, #<Class:Class>, #<Class:Module>, #<Class:Object>, #<Class:Kernel>, #<Class:BasicObject>]
I typically use ancestors view the inheritance chain and get a better idea of Ruby's method lookup. Perhaps the method is not well suited for this task. What task is the ancestors method well suited for?
I think you'll really like the ruby 2.1 then!
$ rbenv local 2.1.0
roman.brodetski#nb-rbrodetski []
$ irb
...
irb(main):008:0> B.singleton_class.ancestors
=> [#<Class:B>, ClassMethods, #<Class:A>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, Kernel, BasicObject]
irb(main):009:0>
I also think it's a great thing that your mind is synchronized with the language developers!
Here is the redmine issue: https://bugs.ruby-lang.org/issues/8035

How to find any methods ancestor chain?

class Taco
# . . .
end
Get ancestor chain:
Taco.ancestors
#=> [Taco, Object, Kernel, BasicObject]
Say I want to find the "parent" class and it's ancestor chain for a ruby defined method. How would I go about doing that?
E.g. method_missing.parent.ancestors
And if everything is supposed to inherit from BasicObject why doesn't Kernel?
Object.ancestors
#=> [Object, Kernel, BasicObject]
Kernel.ancestors
#=> [Kernel]
BasicObject.ancestors
#=> [BasicObject]
Also Class inherits from Class and Module but why does my Taco class ancestor's chain not inherit from them and instead inherits directly from Object forward?
Class.ancestors
#=> [Class, Module, Object, Kernel, BasicObject]
You are looking for owner.
method(:puts).owner
#=> Kernel
method(:puts).owner.ancestors
#=> [Kernel]
Back to your taco example:
class Taco
def self.eat
"YUM"
end
end
Taco.method(:eat).owner
#=> #<Class:Taco>
Taco.method(:eat).owner.ancestors
#=> [Class, Module, Object, PP::ObjectMixin, Kernel, BasicObject]
Kernel is an instance of module. Check this out:
Kernel.class.ancestors
#=> [Module, Object, PP::ObjectMixin, Kernel, BasicObject]
Here is some further reading on the ruby object model if you're interested. Also, here's an image stolen from google images that may help solidify those concepts.
Specifically regarding your question: "Also Class inherits from Class and Module but why does my Taco class ancestor's chain not inherit from them and instead inherits directly from Object forward?"
Taco is an instance of Class, but its superclass is Object. An instance relationship and a superclass relationship are 2 completely different things.
For example: imagine you create a class Animal with a subclass Dog. Animal.new will give you an object which is an instance of Animal. The Dog class is not an instance of Animal (it is not itself an animal -- rather, it defines a type of animal). Its superclass is Animal.
Every object is an instance of some class -- call class to find out which one. But not all objects have a superclass -- only instances of Class do.
Additionally, Class does not inherit from Class. That is impossible. All classes appear at the beginning of their own ancestors, but that doesn't mean they inherit from themselves. That is just how the ancestors method works.

Resources