30 Days of Tech: Day 19 - instance_eval & Method#to_proc
June 19th, 2008
Ruby has instance_eval that accepts a block and executes it with self set to the receiver of instance_eval. Ruby also has a method called method that returns a Method object representing a callable method on an object. Method objects, in turn, have a method called to_proc which allows them to be converted to blocks. These are all facts I knew when the day began, but never before had this question occurred to me: what happens when you pass the result of Method#to_proc into instance_eval? What is self? The answer surprised me actually…
Ruby has instance_eval that accepts a block and executes it with self set to the receiver of instance_eval. Ruby also has a method called method that returns a Method object representing a callable method on an object. Method objects, in turn, have a method called to_proc which allows them to be converted to blocks. These are all facts I knew when the day began, but never before had this question occurred to me: what happens when you pass the result of Method#to_proc into instance_eval? What is self? The answer surprised me actually…
def test_method_must_take_one_argument_for_instance_eval
instance = Class.new {def take_no_args; end}.new
begin
Object.new.instance_eval(&instance.method(:take_no_args))
flunk "No ArgumentError was raised"
rescue ArgumentError => e
assert_equal "wrong number of arguments (1 for 0)", e.message
end
end
def test_argument_to_method_for_instance_eval_is_receiver_of_instance_eval
instance = Class.new {def return_argument(o); o; end}.new
obj = Object.new
assert_equal obj, obj.instance_eval(&instance.method(:return_argument))
end
def test_self_within_instance_eval_is_original_instance
instance = Class.new {def return_self(o); self; end}.new
assert_equal instance, Object.new.instance_eval(&instance.method(:return_self))
end
The first test was the one that surprised me. An ArgumentError being raised would certainly not have been my first guess about what was going to happen. The fact that the error is raised, however, is due to the answer to the “what is self?” question. It turns out that self is left as the object that the method is from—i.e. the block isn’t really evaluated in the context of the instance that received instance_eval. This seems reasonable, since methods must be executed on instances of the classes they are defined for. Allowing instance_eval to change self for a method would break this rule. What is interesting, however, is that a sort compromise has been struck. Instead of setting self to the receiver of instance_eval, the receiver is passed to the method as an argument. Thus if you really need to pass a method object off to some code that is doing instance_eval, you’ll still have access to the object that received the instance_eval message, just in case you need it. I haven’t ever hit this in practice, so I’m not sure whether I’d rather have access to the object or not in general, but I can understand why one might. If you find yourself butting up against this scenario, however, I would encourage you to examine why you’re using Method#to_proc and instance_eval together in the first place.
Sorry, comments are closed for this article.