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.