How are variables related to the body of the define_method method?

While trying to clear my Ruby skills, I continue to work in this case, and I cannot understand the explanations just by reading the API docs. An explanation would be very helpful. Here is a sample code:

for name in [ :new, :create, :destroy ] define_method("test_#{name}") do puts name end end 

I want / expect that the variable name will be bound to the block specified in define_method , and that when #test_new called it will print "new". Instead, each defined method outputs "destroy" - the last value assigned to the variable name. What I donโ€™t understand about define_method and its blocks? Thanks!

+4
source share
3 answers

Blocks in Ruby are closures: the block that you pass to define_method captures the name variable itself, not its value, so it remains in the scope whenever this block is called. This is the first part of the puzzle.

The second part is that the method defined by define_method is the block itself. Basically, it converts a Proc object (the block passed to it) into a Method object and associates it with the receiver.

So, in the end, you get a method that captured (closed) the name variable, which by the time your loop is complete is set to :destroy .

Addition: The for ... in construct actually creates a new local variable that will not be executed by the corresponding [ ... ].each {|name| ... } [ ... ].each {|name| ... } . That is, your for ... in loop is equivalent to the following (in Ruby 1.8 anyway):

 name = nil [ :new, :create, :destroy ].each do |name| define_method("test_#{name}") do puts name end end name # => :destroy 
+6
source
 for name in [ :new, :create, :destroy ] local_name = name define_method("test_#{local_name}") do puts local_name end end 

This method will behave as you expect. The reason for the confusion is that the "name" is not created once per iteration of the for loop. It is created once and increased. In addition, if I understand correctly, method definitions are not closures, like other blocks. They retain the variable visibility, but do not close the current value of the variables.

+1
source

The problem is that for loop expressions do not create a new scope. The only things that create new areas in Ruby are script bodies, module bodies, class bodies, method bodies, and blocks.

If you really look at the behavior of for loop expressions in a draft of the Ruby ISO specification, you will find that the for loop expression executes exactly like each iterator, except that it does not create a new scope.

No Rubyist would ever use a for loop, anyway: instead they would use an iterator that takes a block and thereby creates a new scope.

If you use an idiomatic iterator, everything works as expected:

 class Object %w[new create destroy].each do |name| define_method "test_#{name}" do puts name end end end require 'test/unit' require 'stringio' class TestDynamicMethods < Test::Unit::TestCase def setup; @old_stdout, $> = $>, (@fake_logdest = StringIO.new) end def teardown; $> = @old_stdout end def test_that_the_test_create_method_prints_create Object.new.test_create assert_equal "create\n", @fake_logdest.string end def test_that_the_test_destroy_method_prints_destroy Object.new.test_destroy assert_equal "destroy\n", @fake_logdest.string end def test_that_the_test_new_method_prints_new Object.new.test_new assert_equal "new\n", @fake_logdest.string end end 
0
source

All Articles