rubymetaprogrammingeigenclass

define_method and the object Class: strange behavior



EDIT: Let me expand a bit on what my intentions were:

Given that a ruby object gets its methods from its class' instance methods.

What I was trying to "prove" was that by adding instance methods to that object's class, they would then become method on that object itself.

So:

class Test;end
Test.class.class_eval do 
  def foo
    puts "I am a method in #{self}"
  end
end
Test.foo
=> I am a method in Test
Test.class.foo
=> I am a method in Class

I actually already figured out why this happens. That's because Class.class is... Class! Since Class has foo as part of its instance methods table, and since Class.class points to itself, it will also have foo as a callable instance method.

Original post follows:

Just for the sake of learning more about the Ruby object model, I've been doing some experiments, and the following behavior surprised me:

Let's say we have a class Test:

class Test;end

Then, we class_eval Test.class in order to add a method to the Test object:

Test.class.class_eval do
  def foo 
    puts 'foo called!'
  end
end

(equivalent to calling Test.class.send(:define_method,:foo) ... )

Then:

irb(main):076:0> Test.foo
=> foo called!

But sending foo to class also works:

irb(main):006:0> Test.class.foo
=> foo called!

I still can't figure out why, though. The Class' singleton_class doesn't contain it (the only possible explanation that came to my mind was that somehow the method was added to the Class' singleton class):

Test.class.singleton_methods
=> [:nesting, :constants]

How does the method lookup work in this case? Why does sending :foo to Test.class also calls the method ? Am I missing something ?


As a reference, I did the same thing with an instance of Test:

irb(main):001:0> class Test;end
=> nil
irb(main):002:0> _foo = Test.new
=> #<Test:0x007fa2c39e2f38>
irb(main):003:0> _foo.class.class_eval do
irb(main):004:1*   def foo
irb(main):005:2>    puts 'foo called!'
irb(main):006:2>   end
irb(main):007:1> end
=> :foo
irb(main):008:0> _foo.foo
foo called!
=> nil
irb(main):009:0> _foo.class.foo
NoMethodError: undefined method `foo' for Test:Class

This worked the way I expected it to.

Thanks in advance!


Solution

  • Long story short, sending foo to Test.class works because:

    Test.class == Class and Class.class == Class
    

    Class.class points to itself.