rubyevalinstance-eval

`instance_eval` and scopes


I have the following code:

class A
  def self.scope
    yield
  end
  def self.method_added method
    self.instance_eval %{
      # do something involving the added method 
    }
  end
end

class B < A
  scope do 
    def foo
    end
  end
end

When the method_added hook is fired, will the code inside instance_eval run within the same scope as the method that was added? Or, will it run outside of it?

What are the caveats and gotchas involved within this?


Solution

  • Your scope method is basically a no-op. When you pass a block to a method that yields, the block is evaluated in the current scope. Observe:

    class A
      def self.scope
        yield
      end
    end
    
    A.scope { p self }
    # main
    

    Since nothing is yielded to the block, and nothing is done with the return value of yield, any code run in the block will have the same effect run outside the scope block.

    This isn't the case with instance_eval, however. When instance_eval runs a block, self in the block is set to the receiver (rather than whatever self is in the block's scope). Like this:

    class A
    end
    
    A.instance_eval { p self }
    # A
    

    But note that this means that self.instance_eval { ... } is also a fancy no-op, because you're changing the block's self to the same self outside the block.

    So your code is equivalent to this:

    class A
      def self.method_added method
        # do something involving the added method 
      end
    end
    
    class B < A
      def foo
      end
    end