Instance_eval trickiness

Hi,

I think instance_eval() is the trickiest of the eval’s to understand.
“Metaprogramming Ruby” says there are two concepts that you have to be
aware of:

  1. which object is self
  2. which object is the current class

Unlike self, there is no keyword that you can print out that tells you
what the current class is. The most obvious indicator of what the
current class is in your code is the ‘class’ keyword:

class Dog

end

Between ‘class’ and ‘end’ the current class is Dog. At times it is
important to know what the current class is because def’s attach
themselves to the current class.

Note that inside classes, like Dog, (and outside any method definitions)
it so happens that the current class and self are the same:

class Dog
#in here the current class is Dog

puts self #=> Dog
end

…so the distinction between the current class and self is not clear.

However, instance_eval() pointedly highlights the difference between
self and the current class. The following code traces self:

obj = Object.new
puts obj.object_id

obj.instance_eval do
puts self.object_id
end

–output:–
76644340
76644340

As the results show, inside the instance_eval() block self is equal to
the object calling instance_eval(). But how about the current class?
Which class does a def inside the instance_eval() block attach itself
to?

obj.instance_eval do
def talk
puts ‘hi’
end
end

class <<obj #obj’s singleton class
puts public_instance_methods.grep(/^ta/)
end

–output:–
talk
taint
tainted?
tap

That shows that the current class inside the instance_eval() block has
been switched to the singleton class of the object calling
instance_eval(). In this case, self and the current class are not the
same: self = obj and the current class = obj’s singleton class. That
has implications for what you can do inside the block. For instance,
even though a def will attach itself to the obj’s singleton class, you
cannot call define_method() inside the block:

obj = Object.new
puts obj.object_id

obj.instance_eval do
define_method(:talk) do
puts ‘hi’
end
end

–output:–
prog.rb:5:in block in <main>': undefined methoddefine_method’ for
#Object:0x86a8b5c (NoMethodError)
from prog.rb:4:in instance_eval' from prog.rb:4:in

The error occurs because self = obj, so the method call is implicitly:

(obj.)define_method(…)

and obj is not a class, so obj can’t call define_method().

Why would you ever want to call define_method() when you can just use a
def? Because a def changes the scope and cuts off the visibility of the
local variables outside the def. If you could call define_method() like
this:

greeting = ‘hi’

obj.instance_eval do
define_method(:talk) do
puts greeting
end
end

…then greeting would be visible to the puts statement. But you can’t
do that due to the fact that self and the current class are not the same
inside instance_eval().

obj.instance_eval do
#self = obj
#but the current class = obj’s singleton class
end