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 << ObjectThe above implementation will work for this code:
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
class FooHowever, if you could pass a parameter to the block the code could become:
bubble :return_self do
some_array << 'return_self'
end
bubble :return_self2 do
some_array << 'return_self2'
end
end
class FooUnfortunately, instance_eval does not currently allow you to pass parameters.
bubble :return_self do |method_name|
some_array << method_name.to_s
end
end
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 ObjectUsing instance_exec allows us to use this implementation for bubble:
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
class << ObjectThis allows us to dry up the Foo code to be:
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
class FooFor more info on instance_exec in Ruby 1.9 check out Mauricio's write-up.
bubble :return_value1, :return_value2 do |method_name|
some_array << method_name
end
end
I've written several implementations :) The one you picked is amongst the worst I made, since it doesn't work for immediate values and will also fail with frozen objects.
ReplyDeleteYou can find a better ("#freeze-safe") one at http://eigenclass.org/hiki.rb?instance_exec
I've also written a bounded-space instance_exec (the other ones leaked about 50-70 bytes per call!):
http://eigenclass.org/hiki.rb?bounded+space+instance_exec
Implementing such "basic" functionality correctly is trickier than it seems :)
good information, thanks alot
ReplyDeleteرضا
Thanks for you great posts, maybe you should link this article in the instance_eval context post.
ReplyDeleteKeep rocking!