Back to Blog

instance_exec , changing self and params

on May 28, 2010

Here is updated article on the same topic .

Following code will print 99 as the output.

1class Klass
2  def initialize
3    @secret = 99
4  end
5end
6puts Klass.new.instance_eval { @secret }

Nothing great there. However try passing a parameter to instance_eval .

1puts Klass.new.instance_eval(self) { @secret }

You will get following error.

1wrong 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.

1class Klass
2  def initialize
3    @secret = 99
4  end
5end
6puts 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.

1module Kernel
2  def singleton_class
3    class << self
4      self
5    end
6  end
7end
8
9class Human
10  proc = lambda { puts 'proc says my class is ' + self.name.to_s }
11
12  singleton_class.instance_eval do
13    define_method(:lab)  do
14      proc.call
15    end
16  end
17end
18
19class Developer < Human
20end
21
22Human.lab # class is Human
23Developer.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 .

1module Kernel
2  def singleton_class
3    class << self
4      self
5    end
6  end
7end
8
9class Human
10  proc = lambda { puts 'proc says my class is ' + self.name.to_s }
11
12  singleton_class.instance_eval do
13    define_method(:lab)  do
14      self.instance_exec &proc
15    end
16  end
17end
18
19class Developer < Human
20end
21
22Human.lab # class is Human
23Developer.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.


You might also like

If you liked this blog post, check out similar ones from BigBinary