Stupid error on blocks

Hi all ,

I’m working in Rails but I’m sure this is a 100% Ruby question. I’m
trying to use define_method for the first time, it takes the name of the
method and a block.

I want to make multiple_methods each one with its name like
'sort_by_method1" , “sort_by_method2”, … , “sort_by_methodN”

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 think it’s a bad use of the block, can anyone please help??

Kind Regards

Federico

On 2-okt-2007, at 1:53, Federico B. wrote:

I want to make multiple_methods each one with its name like
'sort_by_method1" , “sort_by_method2”, … , “sort_by_methodN”

Why would you?
http://redcorundum.blogspot.com/2006/10/using-sortby-instead-of-
sort.html

On 2-okt-2007, at 1:53, Federico B. wrote:

I’m working in Rails but I’m sure this is a 100% Ruby question. I’m
trying to use define_method for the first time, it takes the name
of the
method and a block.

Oh sorry didn’t read your case. So you want to select something
presorted by X, right?
I know giving Rails-specific advice is not appreciated on this list
but why don’t do it in the published fashion then

module Sorts
def method_missing(m, *a)
if m.to_s =~ /^sort_by_/
col = m.to_s.gsub(/^sort_by_/, ‘’)
find(:all, :order => “#{col} DESC”)
else
super(m, *a)
end
end
end

class << ActiveRecord::Base
include Sorts
end

AnyActiveRecordDescendant.sort_by_id.map{|f| f.id }
=> [2, 1]

You can optionally extend that to handle things like
“sort_by_foo_and_bar_and_baz” easily. And you certainly don’t want
human_name
because it’s the one with spaces, y’know :wink:

define_method, class methods and blocks are tricky because of
scoping, so I would do it that way.

BTW, i’m also curious how you should use define_method with class
methods.

On 2-okt-2007, at 2:25, Julian T. wrote:

BTW, i’m also curious how you should use define_method with class
methods.

Right.
http://www.thekode.net/ruby/techniques/DefiningMethodsWithClosures.html
http://code.whytheluckystiff.net/metaid/browser/trunk/metaid.rb

Federico B. 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.

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.

mortee

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:

CORRECT :

names.each do |name|
  define_method("m1_#{name}") do
    puts "1_#{name}"
  end
end

INCORRECT !

for name in names do
  define_method("m2_#{name}") do
    puts "2_#{name}"
  end
end

Wow thanks Robert that was of help …

So if i get this straight : what happens is that in the execution time
as the for iterated over all the array until the end the variable that
would be the one that gets printed out. If we use each to iterate over a
block the scope of the name variable is saved and stays the same (it
closes on itself) thus we get the correct name variable everytime.

Am i right !?

Regards

Thanks Julian !

Very Useful all your post I learned some nice things…

Yeah it was probably better the method_missing approach than the
define_method !!

Thanks

Julian T. wrote:

On 2-okt-2007, at 2:25, Julian T. wrote:

BTW, i’m also curious how you should use define_method with class
methods.

Right.
http://www.thekode.net/ruby/techniques/DefiningMethodsWithClosures.html
http://code.whytheluckystiff.net/metaid/browser/trunk/metaid.rb

2007/10/2, mortee [email protected]:

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).

  @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

Federico B. wrote:

    puts "1_#{name}"
end

I’d still point out that using both of the above code, the name variable
becomes part of the defined methods’ default binding (which in the first
case is shared among the methods, in the second case are per-method).
This means that if you happen to alter the variable, it’ll keep its new
value for subsequent calls to the methods.

If you use the eval way, then the define-time value of the variable gets
evaluated, the value itself becomes part of the method code, and there
remain no local variables in the methods’ default binding.

Someone correct me if I’m wrong.

mortee

2007/10/2, Federico B. [email protected]:

  define_method("m1_#{name}") do
  end

Am i right !?

Yep. With #each you get multiple bindings that contain the same name
because the block opens a new scope. With for you just get one
binding that contains the variable and so all created methods share
that same value because they fetch it from the binding. The concept
is known as closure. That’s the reason why this works:

$ ruby/counter.rb
c1:1
c1:2
c1:3
c2:101
c2:102
c2:103
c2:104
c2:105

RKlemme@padrklemme1 ~
$ cat ruby/counter.rb
#!ruby

def create_counter(initial = 0)
lambda { initial += 1 }
end

c1 = create_counter
c2 = create_counter 100
3.times { print “c1:”, c1.call, “\n” }
5.times { print “c2:”, c2.call, “\n” }

The lambdas carry around the binding to the scope of the method
create_counter when it was invoked. As you can see, there were two
invocations and so you get two independent counters. The example is
similar to the #each version. As you can see in this example, both
closures share the same environment and thus share the counter value:

RKlemme@padrklemme1 ~
$ ruby/counter-2.rb
c1:1
c1:2
c1:3
c2:5
c2:7
c2:9
c2:11
c2:13
c1:14
c1:15
c1:16

RKlemme@padrklemme1 ~
$ cat ruby/counter-2.rb
#!ruby

def create_counter(initial = 0)
return lambda { initial += 1 }, lambda { initial += 2 }
end

c1, c2 = create_counter
3.times { print “c1:”, c1.call, “\n” }
5.times { print “c2:”, c2.call, “\n” }
3.times { print “c1:”, c1.call, “\n” }

Kind regards

robert