Reputation: 12222
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´ ?
Upvotes: 3
Views: 1429
Reputation: 95318
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.
Upvotes: 5
Reputation: 5545
Inspired on the comment of Niklas B.:
class TestClass
def exec_block(&block) ; yield ; end
end
s = self
TestClass.new.send(:exec_block) do ; puts s ; end
# -> main
TestClass.send(:define_method, :bing) do ; puts s ; end
TestClass.new.bing
# -> main
Upvotes: 0
Reputation: 6720
First thing - when you passing a block explicitly to the method instead of yield
you can use block.call
.
Second thing - inside exec_block
method replace yield
with instance_eval(&block)
and you will see the magic ;)
Little bit more clarification - in the first example the block catches the local scope along with self
variable which is pointing to main
object.
In the second example (with define_method
) the block will be treated as a new method body and it will be evaluated inside object's scope using instance_eval
. For more details you can check: http://apidock.com/ruby/Module/define_method
Upvotes: 0