Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Ruby

A weird bug when using Ruby define_methd inside for loop

I try to use Ruby define_method to dynamically define a class like this:

class Foo 
  def bar1
    return 1
  end
  def bar2
    return 2
  end
  def bar3
    return 3
  end
end

Here is my implementation:

class Foo
  for i in 1..3
    define_method("bar#{i}") do 
      return i
    end
  end 
end

When I run the following three commands, all of them return 3

Foo.new.bar1 # return 3
Foo.new.bar2 # return 3
Foo.new.bar3 # return 3

Anyone know how to fix this?


I kinds of answer my question below.....

3 Answers

Hey Nathan Williams and Gaylen Miller,

Thanks for your replies. Your answers reminds me that this weird bug may be caused by the way how Ruby FOR loop handles closure. I found my answer in this StackOverflow post: http://stackoverflow.com/questions/10396475/ruby-for-loop-a-trap

Ruby for loop create one scope during the whole execution. It can be best explained using the following code:

results = []
for i in 1..3
  results << lambda { i }
  p results.map(&:call)
  # during the 1st loop, the output is [1]
  # during the 2nd loop, the output is [2, 2]
  # during the 3rd loop, the output is [3, 3, 3]
end
p results.map(&:call)  # => [3,3,3]

By the time when the FOR loop finishes execution, the variable i inside the scope of the for loop is set to the value 3. All of variables i in the anonymous function points this i.

Nathan Williams
seal-mask
.a{fill-rule:evenodd;}techdegree
Nathan Williams
Python Web Development Techdegree Student 6,851 Points

Hmmmm... I'm not sure exactly what's going on there either (though now I'm really curious), but this should do what you're looking for:

[nathwill@wyrd ~]$ irb
irb(main):013:0> 3.times do |i|
irb(main):014:1* define_method("bar#{i}") do
irb(main):015:2* return i
irb(main):016:2> end
irb(main):017:1> end
=> 3
irb(main):018:0> bar1
=> 1
irb(main):019:0> bar2
=> 2
irb(main):020:0> bar3
=> 3

Hope that helps!

It seems that there is somethings wrong with for loop.

When I change the code to this

class Bar
  1.upto(3) do |i|
    self.send(:define_method,"foo_#{i}") do
      return i
    end
  end
end 

or this:

class Bar
  [1,2,3].each do |i|
    self.send(:define_method,"foo_#{i}") do
      return i
    end
  end
end 

and even this:

class Bar
  (1..3).each do |i|
    self.send(:define_method,"foo_#{i}") do
      return i
    end
  end
end 

It works!

Here's at least part of the explanation. With the For loop, there are three methods being defined to view the same object.

irb(main):001:0> class Foo                                                                                                                                                                                                                                        
irb(main):002:1>   for i in 1..3 do                                                                                                                                                                                                                               
irb(main):003:2*     define_method("bar#{i}") {"#{i} #{i.object_id}"}                                                                                                                                                                                             
irb(main):004:2>   end                                                                                                                                                                                                                                            
irb(main):005:1> end                                                                                                                                                                                                                                              
=> 1..3                                                                                                                                                                                                                                                           
irb(main):006:0> Foo.new.bar1                                                                                                                                                                                                                                     
=> "3 7"                                                                                                                                                                                                                                                          
irb(main):007:0> Foo.new.bar2                                                                                                                                                                                                                                     
=> "3 7"                                                                                                                                                                                                                                                          
irb(main):008:0> Foo.new.bar3                                                                                                                                                                                                                                     
=> "3 7"                                                                                                                                                                                                                                                          
irb(main):009:0>                       

With the "times" way, there are three different objects being displayed.

irb(main):011:0> 3.times do |i|                                                                                                                                                                                                                                   
irb(main):012:1*   define_method("bar#{i}") {"#{i} #{i.object_id}"}                                                                                                                                                                                               
irb(main):013:1> end                                                                                                                                                                                                                                              
=> 3                                                                                                                                                                                                                                                              
irb(main):014:0> bar0                                                                                                                                                                                                                                             
=> "0 1"                                                                                                                                                                                                                                                          
irb(main):015:0> bar1                                                                                                                                                                                                                                             
=> "1 3"                                                                                                                                                                                                                                                          
irb(main):016:0> bar2                                                                                                                                                                                                                                             
=> "2 5"