May 28, 2010
Here is updated article on the same topic .
Following code will print 99
as the output.
class Klass
def initialize
@secret = 99
end
end
puts Klass.new.instance_eval { @secret }
Nothing great there. However try passing a parameter to instance_eval
.
puts Klass.new.instance_eval(self) { @secret }
You will get following error.
wrong number of arguments (1 for 0)
So instance_eval
does not allow you to pass parameters to a block.
instance_exec
was added to ruby 1.9 and it allows you to pass parameters to a
proc. This feature has been backported to ruby 1.8.7 so we don't really need
ruby 1.9 to test this feature. Try this.
class Klass
def initialize
@secret = 99
end
end
puts Klass.new.instance_exec('secret') { |t| eval"@#{t}" }
Above code works. So now we can pass parameters to block. Good.
Another feature of instance_exec
is that it changes the value of self
. To
illustrate that I need to give a longer example.
module Kernel
def singleton_class
class << self
self
end
end
end
class Human
proc = lambda { puts 'proc says my class is ' + self.name.to_s }
singleton_class.instance_eval do
define_method(:lab) do
proc.call
end
end
end
class Developer < Human
end
Human.lab # class is Human
Developer.lab # class is Human ; oops
Notice that in that above case Developer.lab
says "Human". And that is the
right answer from ruby perspective. However that is not what I intended. ruby
stores the binding of the proc in the context it was created and hence it
rightly reports that self is "Human" even though it is being called by
Developer
.
Go to
http://facets.rubyforge.org/apidoc/api/core/index.html
and look for instance_exec
method. The doc says
Evaluate the block with the given arguments within the context of this object, so self is set to the method receiver.
It means that instance_exec
evaluates self in a new context. Now try the same
code with instance_exec
.
module Kernel
def singleton_class
class << self
self
end
end
end
class Human
proc = lambda { puts 'proc says my class is ' + self.name.to_s }
singleton_class.instance_eval do
define_method(:lab) do
self.instance_exec &proc
end
end
end
class Developer < Human
end
Human.lab # class is Human
Developer.lab # class is Developer
In this case Developer.lab
says Developer
and not Human
.
You can also checkout this page (Link is not available) which has much more
detailed explanation of instance_exec
and also emphasizes that instance_exec
does pass a new value of self
.
instance_exec
is so useful that ActiveSupport
needs it. And since ruby 1.8.6
does not have it ActiveSupport
has code to support it.
I came across instance_exec
issue while resolving
#4507 rails ticket
. The final solution did not need instance_exec
but I learned a bit about it.
If this blog was helpful, check out our full blog archive.