Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the relationship between the metaclass of Base and Derived class in Ruby?

In Ruby, we could use super within singleton method to call the corresponding super class's singleton method, like the following code shows.

class Base
  def self.class_method
    puts "Base class method"
  end
end

class Derived < Base
  def self.class_method
    puts "Derived class method"
    super
  end
end

Derived.class_method
# Derived class method
# Base class method

However, I don't seem quite get how that call to super within Derived.class_method could reach Base.class_method. I'd assume that class_method is defined on their metaclass, does that mean their metaclass has parent/child relationship? (I can't quite confirm that by experiments)

Update: I'm asking this question because I remembered seeing somewhere there's some kind of relationship bettwen base and derived class' metaclass (but I can't find it any more). In addition to know how actually super works, I'd also like to confirm whether the two metaclasses are totally separate or not.

like image 567
bryantsai Avatar asked Jan 26 '10 06:01

bryantsai


People also ask

What is metaclass in Ruby?

Class method frontend that we defined earlier is nothing but an instance method defined in the metaclass for the object Developer ! A metaclass is essentially a class that Ruby creates and inserts into the inheritance hierarchy to hold class methods, thus not interfering with instances that are created from the class.

What is the relationship between classes and modules?

Classes may generate instances (objects), and have per-instance state (instance variables). Modules may be mixed in to classes and other modules. The mixed in module's constants and methods blend into that class's own, augmenting the class's functionality. Classes, however, cannot be mixed in to anything.

What is MetaProgramming in Ruby?

Metaprogramming is a technique in which code operates on code rather than on data. It can be used to write programs that write code dynamically at run time. MetaProgramming gives Ruby the ability to open and modify classes, create methods on the fly and much more.

How do you extend a class in Ruby?

[extend] - will add the extended class to the ancestors of the extending classes' singleton class. Other words extend mix the module's functionality into class and makes these methods available to the class itself. [super] - is used for overridden method call by the overriding method.


2 Answers

There are four class objects in play here:

<Class>---class---><Class>
Base               #Base
   ^                  ^
   |                  |
   |                  |
 super              super
   |                  |
   |                  |
<Class>            <Class>
Derived---class--->#Derived

Nomenclature:

  • <...> is the class of each object.
  • The name of the class is on the second line.
  • If the name starts with #, it's the eigenclass (aka singleton class).
  • super points to a class's superclass
  • class points to the class's class.

When you call Derived.class_method, Ruby follows the "right one and then up" rule: First go to the object's class, then follow the superclass chain up, stopping when the method is found:

  • The receiver of the "class_method" call is Derived. So follow the chain right to Derived's class object, which is its eigenclass (#Derived).
  • Derived does not define the method, so Ruby follows the chain up the chain to #Derived's superclass, which is #Base.

  • The method is found there, so Ruby dispatches the message to #Base.class_method

You don't think I knew all this stuff off the top of my head, did you? Here's where my brain got all this meta juju: Metaprogramming Ruby.

Part 2. How to make an "eigenclass" (aka "singleton class") come out of hiding

class Object
  def eigenclass
    class << self
      self
    end
  end
end

This method will return the eigenclass of any object. Now, what about classes? Those are objects, too.

p Derived.eigenclass               # => #<Class:Derived>
p Derived.eigenclass.superclass    # => #<Class:Base>
p Base.eigenclass                  # => #<Class:Base>

Note: Above is from Ruby1.9. When run under Ruby 1.8, you get a surprise:

p Derived.eigenclass.superclass    # => #<Class:Class>
like image 104
Wayne Conrad Avatar answered Oct 21 '22 06:10

Wayne Conrad


To clarify and correct what i wrote in the comments regarding the way Ruby hides/exposes eigenclasses, here is the situation:

Ruby 1.8:

(1) The Object#class method always returns the first real (non eigenclass or iclass) superclass of the actual class of an object. e.g

o = Object.new
class << o; end
o.class #=> returns Object, even though the _actual_ class is the eigenclass of o

In other words, the Object#class method will never return an eigenclass, it passes over them and instead returns the first 'real' class it finds in the inheritance hierarchy.

(2) The Class#superclass method has two cases. If the receiver is not an eigenclass then it simply returns the superclass. However, if the receiver is an eigenclass then this method returns the actual (i.e not necessarily real) class of the receiver.

# case where receiver is a normal class (i.e not an eigenclass)
Module.superclass #=> Object (behaves as expected)

# case where receiver is an eigenclass
class << Module; superclass; end #=> returns Class, this is NOT the superclass

From above, Class#superclass behaves as expected in the case of a normal class, but for the eigenclass example it states the superclass of the eigenclass of Module is Class which is not true. From this diagram http://banisterfiend.wordpress.com/2008/10/25/the-secret-life-of-singletons/ we know that the superclass of the eigenclass of Module is actually the eigenclass of Object. I am unsure why Ruby 1.8 has this strange behaviour.

Ruby 1.9:

(1) The Object#class method behaves identically to the 1.8 version.

(2) The Class#superclass method no longer has two cases, it now treats eigenclasses the same way it treats normal classes and returns the actual superclass as expected.

e.g

class << Module; superclass; end #=> #<Class:Object>
like image 29
horseyguy Avatar answered Oct 21 '22 05:10

horseyguy