define_method exhibits the following behaviour:
class TestClass
def exec_block(&block) ; yield ; end
end
TestClass.new.send(:exec_block) do ; puts self ; end
# -> main
TestClass.send(:define_method, :bing) do ; puts self ; end
TestClass.new.bing
# -> <TestClass:...>
What I do not understand is that the the block passed to define_method is supposed to be a closure. As such it should (at least according to my understanding) capture the value of self
as main
, as exhibited when calling exec_block
.
I understand that the block will become the body of the method, yet I do not understand the reason for the behavior. Why does the block evaluate to different things when used with different methods ?
How can I reproduce the behavior of block with define_method
for other methods ? i.e. how could I write exec_block
to have it output <TestClass:...>
instead of `main´ ?
self
is captured by the closure like any other variable. We can verify that by passing a Proc
around different object instances:
class A
def exec_block(&block)
block.call
end
end
class B
def exec_indirect(&block)
A.new.exec_block(&block)
end
end
block = proc { p self }
a = A.new; b = B.new
a.exec_block(&block) # => main
b.exec_indirect(&block) # => main
However, BasicObject#instance_eval
and the alikes rebind the self
variable dynamically:
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
Module#define_method
in turn uses instance_eval
to execute the associated block:
If a block is specified, it is used as the method body. This block is evaluated using instance_eval [...]
Observe:
A.send(:define_method, :foo, &block)
a.foo # => #<A:0x00000001717040>
a.instance_eval(&block) # => #<A:0x00000001717040>
With that knowledge, you can now rewrite your exec_block
to use instance_eval
:
class A
def exec_block(&block)
instance_eval(&block)
end
end
block = proc { p self }
A.new.exec_block(&block) # => #<A:0x00000001bb9828>
As mentioned before, using instance_eval
seems to be the only way to run a Proc
instance with a modified context. It can be used to implement dynamic binding in Ruby.