Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby method lookup path for an object

Tags:

ruby

Is there a built-in Ruby method or a well-known library that returns the entire method lookup chain for an object? Ruby looks through a confusing sequence of classes (as discussed in this question) for instance methods that correspond with a message and calls method_missing on the receiver if none of the classes respond to the message.

I put together the following code, but am sure it is missing certain cases or if it's 100% right. Please point out any flaws and direct me to some better code if it exists.

def method_lookup_chain(obj, result = [obj.singleton_class])
  if obj.instance_of? Class
    return add_modules(result) if result.last == BasicObject.singleton_class
    r = result.last.superclass
    method_lookup_chain(obj, result << r)
  else
    return result + obj.class.ancestors
  end
end

def add_modules(klasses)
  r = []
  klasses.each_with_index do |k, i|
    r << k
    next if k == klasses.last
    r << (k.included_modules - klasses[i+1].included_modules)
  end
  r.flatten
end

# EXAMPLES

module ClassMethods; end
module MoreClassMethods; end
class A
  extend ClassMethods
  extend MoreClassMethods
end
p method_lookup_chain(A) # => [#<Class:A>, MoreClassMethods, ClassMethods, #<Class:Object>, #<Class:BasicObject>]

module InstanceMethods; end
class Object
  include InstanceMethods
end
module X; end
module Y; end
class Dog
  include X
  include Y
end
d = Dog.new
p method_lookup_chain(d) # => [#<Class:#<Dog:0x007fcf7d80dd20>>, Dog, Y, X, Object, InstanceMethods, Kernel, BasicObject]
like image 950
Powers Avatar asked May 24 '14 19:05

Powers


People also ask

What is method lookup?

Method lookup is a simple affair in most languages without multiple inheritance. You start from the receiver and move up the ancestors chain until you locate the method. Because Ruby allows you to mix in modules and extend singleton classes at runtime, this is an entirely different affair.

What is Respond_to in Ruby?

respond_to is a Rails method for responding to particular request types. For example: def index @people = Person.find(:all) respond_to do |format| format.html format.xml { render :xml => @people.to_xml } end end.

What is Module_function in Ruby?

module_function(*args) private. Creates module functions for the named methods. These functions may be called with the module as a receiver, and also become available as instance methods to classes that mix in the module. Module functions are copies of the original, and so may be changed independently.


1 Answers

That other post makes it seem confusing, but it really isn't. If you are interested in such things, you should read "Metaprogramming Ruby". Until then, the basic rule is one step to the right and up:

          Object (superclass)
              ^
              |
          Parent class A(superclass)
              ^
              |
          Parent class B(superclass)
              ^
              |
obj  ->   object's class

2) Singleton classes are inserted between the obj and the object's class:

          Object
              ^
              |
          Parent class A(superclass)
              ^
              |
          Parent class B(superclass)
              ^
              |
          object's class(superclass)
              ^
              |
obj  ->   obj's singleton_class

3) Included modules are inserted immediately above the class that does the including:

          Object
              ^
              |
          Parent class A
              ^
              |
              Module included by Parent Class B
              ^
              |
          Parent class B
              ^
              |
          object's class
              ^
              |
obj  ->   obj's singleton_class

Edit:

Please point out any flaws

p method_lookup_chain(Class)

--output:--
[#<Class:Class>, #<Class:Module>, #<Class:Object>, #<Class:BasicObject>]

But...

class Object
  def greet
    puts "Hi from an Object instance method"
  end
end

Class.greet

--output:--
Hi from an Object instance method

And..

class Class
  def greet
    puts "Hi from a Class instance method"
  end
end

Class.greet

--output:--
Hi from a Class instance method

The lookup path for a method called on a class actually continues past BasicObject's singleton class(#<Class:BasicObject>):

class BasicObject
  class <<self
    puts superclass
  end
end

--output:--
Class

The full lookup path for a method called on Class looks like this:

                  Basic Object                 
                      ^
                      |
                    Object
                      ^
                      |
                    Module
                      ^
                      |
                    Class
                      ^
                      |
BasicObject    BasicObject's singleton class                
  |                   ^
  |                   |
Object         Object's singleton class
  |                   ^
  |                   |
Module         Module's singleton class
  |                   ^
  |                   |
Class  --->    Class's singleton class

The lookup starts in Class's singleton class and then goes up the hierarchy on the right. "Metaprogramming Ruby" claims there is a unified lookup theory for all objects, but the lookup for methods called on a class does not fit the diagram in 3).

You have the same problem here:

class A 
end

class B < A
end

p method_lookup_chain(B)

--output:--
[#<Class:B>, #<Class:A>, #<Class:Object>, #<Class:BasicObject>]

It should be this:

                  Basic Object                 
                      ^
                      |
                    Object
                      ^
                      |
                    Module
                      ^
                      |
                    Class
                      ^
                      |
BasicObject    BasicObject's singleton class
  |                   ^
  |                   |
Object         Object's singleton class
  |                   ^
  |                   |
  A            A's singleton class
  |                   ^
  |                   |
  B.greet -->  B's singleton class

One thing you need to keep in mind: the lookup path of any method called on a class has to include Class somewhere because ALL classes inherit from Class.

like image 182
7stud Avatar answered Sep 24 '22 07:09

7stud