instance_exec , changing self and params
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 paramter 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.
How to get around to the restriction that instance_eval does not accept parameters
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.
Changing value of self
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 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.