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.
def instance_exec(*args, &block)
mname = "__instance_exec_#{Thread.current.object_id.abs}"
metaclass.class_eval{ define_method(mname, &block) }
begin
ret = send(mname, *args)
ensure
metaclass.class_eval{ undef_method(mname) } rescue nil
end
ret
end
def metaclass
class << self
self
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.