Question

class MyClass
  def one
    def two
    end
  end
end

obj = MyClass.new
obj.one
puts obj.method(:two).owner  #==> MyClass

Here i define method two inside another method one. Method one is called by the instance of MyClass (obj) . So the self is obj when the method two is defined. when i check the owner of method two it's MyClass

obj.instance_eval do
  def three
  end
end

puts obj.method(:three).owner  #==> #<Class:#<MyClass:0x007f85db109010>>

In this snippet i do instance_eval on obj , so the self is again obj when the method three is defined . But when i check the owner of three , it's the singleton class of obj

why is this? is there anything else besides self which determines where the method definition goes ??

Was it helpful?

Solution 2

This is explained in a nice article by ruby-core contributor yugui: Three implicit contexts in Ruby. Basically, there is a default definition context, which is not the same as self. Methods that are not explicitly defined as singleton methods end up as instance methods of the default definition context. module and class definition bodies change the default definition context whereas def doesn't. instance_eval OTOH does change it.

OTHER TIPS

I would use Kernel#set_trace_func method,to explain you what is going on under the hood. Look first the below code and the output:

trace = lambda do |event,file,line,id,binding,klass|
    p [event,File.basename(file),line,id,binding,klass]
end


set_trace_func trace

class MyClass
  def self.bar;end
  def one
    def two
    end
  end
end

obj = MyClass.new
obj.one
obj.instance_eval do
  def three
  end
end

output:

-----------------
----------------
-----------------
-----------------
-----------------
----------------- # part A
["c-call", "test.rb", 9, :singleton_method_added, #<Binding:0x83ab2b0>, BasicObject]
["c-return", "test.rb", 9, :singleton_method_added, #<Binding:0x83aaeb4>, BasicObject]
["line", "test.rb", 10, nil, #<Binding:0x83aab80>, nil]
["c-call", "test.rb", 10, :method_added, #<Binding:0x83aa900>, Module]
["c-return", "test.rb", 10, :method_added, #<Binding:0x83aa07c>, Module]
----------------------------- # part B
["line", "test.rb", 16, nil, #<Binding:0x83a976c>, nil]
["c-call", "test.rb", 16, :new, #<Binding:0x83a9488>, Class]
["c-call", "test.rb", 16, :initialize, #<Binding:0x83a90a0>, BasicObject]
["c-return", "test.rb", 16, :initialize, #<Binding:0x83a8e20>, BasicObject]
["c-return", "test.rb", 16, :new, #<Binding:0x83a8b28>, Class]
---------------------------
---------------------------
--------------------------- # part C
["c-call", "test.rb", 11, :method_added, #<Binding:0x83a7de0>, Module]
["c-return", "test.rb", 11, :method_added, #<Binding:0x83a79f8>, Module]
--------------------------- # part D
["line", "test.rb", 18, nil, #<Binding:0x83a7034>, nil]
["c-call", "test.rb", 18, :instance_eval, #<Binding:0x83a6c10>, BasicObject]
["line", "test.rb", 19, nil, #<Binding:0x83a65f8>, nil]
["c-call", "test.rb", 19, :singleton_method_added, #<Binding:0x83a61d4>, BasicObject]
["c-return", "test.rb", 19, :singleton_method_added, #<Binding:0x83a5ef0>, BasicObject]
["c-return", "test.rb", 18, :instance_eval, #<Binding:0x83a5d4c>, BasicObject]

Explanation:

Look at the 5 lines below part A. It simply tells us the when Ruby will find def key word inside a class, it will add that method as an instance method to that class. This is being done by calling the hook method Module#method_added. The same explanation goes to two lines below part C.

Now what goes on inside obj.instance_eval {..} ?

Ok,this will be cleared if you look at the lines below part D. Look from last, first and second line.Inside the instance_eval block, def third, causes third to be added as a singleton_method of the object ob, by calling the hook method BasicObject#singleton_method_added.

This is how MRI has been written.

def is not a method, so it doesn't need to behave like a method does when it comes to self. It's confusing because these two are obviously equivalent:

class Foo
  def one
    "one"
  end

  define_method(:two) { "two" }
end

While these two are obviously not (instances of Bar don't have define_method)

class Bar
  def one
    def two
      "two"
    end
    "one"
  end

  def three
    define_method(:four) { "four" }
    "three"
  end
end

You could view this as an argument for why the nested def is owned by the class. It makes sense because this kind of nested def is only possible when you've opened the class scope, so it affects every instance.

On the flipside, it definitely makes sense that def in instance_eval gets added to the singleton class because you've explicitly opened the instance only. It would break encapsulation for it to work the same as the other case.

So... basically its exceptional behavior. But it's not entirely nonsensical.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top