From: Robert Klemme Date: 2007-10-02T18:10:13+09:00 Subject: Re: Stupid error on blocks 2007/10/2, mortee : > Federico Brubacher wrote: > > The names are all right but within the block I get for all the methods I > > created my last element (element N) in my $ingredients_column variable . > > > > for column_name in $ingredients_columns do > > > > define_method("sort_by_" + column_name.human_name) { @ingredients = > > Ingredient.find(:all, :order => "#{column_name.human_name } DESC")} > > > > end > > I'm not really experienced, but I guess your problem is caused because > the block you pass to define_method isn't evaluated at the time you pass > it to define_method, but when you actually call the created methods. > This means the local variables are evaluated at that time too. That's part of the explanation. The other part is scoping because the iteration variable in a for loop belongs to the outer scope and thus values are overwritten all the time (see below). > You may want to use eval instead if you want variables expanded at > define-time, and their value be included in the body of the newly > defined methods (the string you pass to eval gets interpolated first > (variable references expanded), and then the resulting ruby code > executed - thus the values effective just when eval is called get > incorporated in the defined methods' bodies): > > for column_name in $ingredients_columns do > eval %Q[ > def sort_by_#{column_name.human_name} { > @ingredients = Ingredient.find( > :all, :order => "#{column_name.human_name} DESC") > } > ] > end > > If anyone knows of a better approach, please post it as I'd be > interested too. That's one way to do it. Actually only a smaller change is needed because the root cause in this case is the scoping rule of the iteration variable in a "for" loop which is different than when iterating with a block as this example demonstrates: RKlemme@padrklemme1 ~ $ ruby/names.rb 1_foo 2_baz 1_bar 2_baz 1_baz 2_baz RKlemme@padrklemme1 ~ $ cat ruby/names.rb #!ruby class Tester def self.create_methods_1(names) names.each do |name| define_method("m1_#{name}") do puts "1_#{name}" end end end def self.create_methods_2(names) for name in names do define_method("m2_#{name}") do puts "2_#{name}" end end end end method_names = %w{foo bar baz} Tester.create_methods_1 method_names Tester.create_methods_2 method_names t=Tester.new method_names.each do |n| t.send "m1_#{n}" t.send "m2_#{n}" end Kind regards robert