Friday, September 08, 2006

Ruby instance_exec aka instance_eval with parameters

Updated: Changed the instance_exec implementation to a better version, also written by Mauricio. See comments for more info.

Recently we were extracting some common behavoir to a class method. The behavior of the methods being defined was similar; however, it did differ slightly so we needed the ability to pass a block to the method. Unfortunately, we needed the block to take a parameter and also execute in the scope of the instance.

For example, the bubble method from my previous example is used to define methods dynamically. We also needed the bubble method to add a string to an array.
class << Object
def bubble(*args, &block)
args.each do |method_name|
define_method(method_name) do
instance_eval(&block) if block_given?
self
end
end
end
end
The above implementation will work for this code:
class Foo
bubble :return_self do
some_array << 'return_self'
end
bubble :return_self2 do
some_array << 'return_self2'
end
end
However, if you could pass a parameter to the block the code could become:
class Foo
bubble :return_self do |method_name|
some_array << method_name.to_s
end
end
Unfortunately, instance_eval does not currently allow you to pass parameters.

In Ruby 1.9 instance_exec should solve this problem; however, for the time being you can find an implementation for 1.8.4 that was written by Mauricio Fernandez.
class Object
module InstanceExecHelper; end
include InstanceExecHelper
def instance_exec(*args, &block)
begin
old_critical, Thread.critical = Thread.critical, true
n = 0
n += 1 while respond_to?(mname="__instance_exec#{n}")
InstanceExecHelper.module_eval{ define_method(mname, &block) }
ensure
Thread.critical = old_critical
end
begin
ret = send(mname, *args)
ensure
InstanceExecHelper.module_eval{ remove_method(mname) } rescue nil
end
ret
end
end
Using instance_exec allows us to use this implementation for bubble:
class << Object
def bubble(*args, &block)
args.each do |method_name|
define_method(method_name) do
instance_exec(method_name, &block) if block_given?
self
end
end
end
end
This allows us to dry up the Foo code to be:
class Foo
bubble :return_value1, :return_value2 do |method_name|
some_array << method_name
end
end
For more info on instance_exec in Ruby 1.9 check out Mauricio's write-up.
Post a Comment