Search code examples
rubyblock

Evaluate a block in a certain scope and pass an argument


For example, let's say I have

block = proc { |n| "#{downcase} #{n}" }

Now I want to evaluate that block in the scope of a String, but pass that block a variable. I know how to do the first part:

"Foo".instance_eval(&block)

But how to also pass a variable to that block?

I tried

"Foo".instance_eval { block.call(3) }

But it didn't work, it wasn't in the scope of the String.


Solution

  • Use instance_exec instead:

    instance_exec(arg...) {|var...| block } → obj

    Executes the given block within the context of the receiver (obj). In order to set the context, the variable self is set to obj while the code is executing, giving the code access to obj’s instance variables. Arguments are passed as block parameters.

    So this will make it go:

    "Foo".instance_exec(3, &block)
    

    and give you the 'foo 3' that you desire.

    The problem with this:

    "Foo".instance_eval { block.call(3) }
    

    is that self will be "Foo" inside { block.call(3) } but not inside block, block will retain whatever self was when block was defined; there's nothing in block.call(3) that forces a context so self doesn't change. For example, given this:

    class C
      def m
        proc { |n| "#{downcase} #{n}" }
      end
    end
    c = C.new
    "Foo".instance_eval { c.m.call(3) }
    

    When the proc is called, self will be c because that's what self was when the proc was defined (i.e. when m was called). What self is inside the instance_eval block doesn't have any effect on self inside the proc.