4

I would like to understand how define_method works and how to properly use the variables outside of the definition block. Here is my code:

class Test
  def self.plugin
    for i in 1..2
      define_method("test#{i}".to_sym) do
        p i
      end
    end
  end
  plugin
end

ob = Test.new
ob.test1 #=> 2  (I would expect 1)
ob.test2 #=> 2  (I would expect 2)

It seems that in the methods test1 and test2, the value of i is not substituted during the definition, but is computed directly on the spot when the method is called. So we see only the latest value of i, which is 2. But where does Ruby take this value from? And is there a way to let test#{i} print i?

In this particular case, I could do a workaround using __method__, but probably there is a better solution.

Community
  • 1
  • 1
overdawn
  • 321
  • 3
  • 10
  • power of _closure_ is expressed here.......... :) – Arup Rakshit May 30 '15 at 10:40
  • Thanks for the link, the question addresses the same problem. But I am not sure the solution is provided there. At least one of the comments does not work:
    class Test
      for name in [ :new, :create, :destroy ]
        local_name = name
        define_method("test_#{local_name}") do
          puts local_name
        end
      end
    end
    
    ob = Test.new
    ob.test_new #=> destroy  (I would expect 'new')
    ob.test_create #=> destroy  (I would expect 'new')
    
    – overdawn May 30 '15 at 11:39
  • Well.. Unmarked. But here is a [similar post](http://stackoverflow.com/questions/2029524/how-are-variables-bound-to-the-body-of-a-define-method). – Arup Rakshit May 30 '15 at 11:42
  • I've taken the code (sorry for markup) exactly from the [similar post](http://stackoverflow.com/questions/2029524/how-are-variables-bound-to-the-body-of-a-define-method) you mentioned. Just wanted to point out that the solution is proposed only in the second comment there and it does not work:-) – overdawn May 30 '15 at 11:51
  • I cannot parse the part "... the value `i` of is not ...". – sawa May 30 '15 at 11:53
  • @sawa fixed, is it ok now? – overdawn May 30 '15 at 12:09

1 Answers1

6

As mentionned in comments this is down to closures - the block passed to define_method captures the local variables from its scope (and not just their value as you found out).

for doesn't create a new scope, so your method 'sees' the change to i. If you use a block (for example with each) then a new scope is created and this won't happen. You just need to change your code to

class Test
  def self.plugin
    (1..2).each do |i|
      define_method("test#{i}".to_sym) do
        p i
      end
    end
  end
  plugin
end

which is basically what the question linked by Arup was about

Frederick Cheung
  • 83,189
  • 8
  • 152
  • 174