[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Stupid error on blocks

Fefo

10/1/2007 11:53:00 PM

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
--
Posted via http://www.ruby-....

9 Answers

Julian 'Julik' Tarkhanov

10/2/2007 12:11:00 AM

0


On 2-okt-2007, at 1:53, Federico Brubacher 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-i...
sort.html
--
Julian 'Julik' Tarkhanov
please send all personal mail to
me at julik.nl



Julian 'Julik' Tarkhanov

10/2/2007 12:26:00 AM

0


On 2-okt-2007, at 1:53, Federico Brubacher 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 ;-)

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.

--
Julian 'Julik' Tarkhanov
please send all personal mail to
me at julik.nl



Julian 'Julik' Tarkhanov

10/2/2007 12:43:00 AM

0


On 2-okt-2007, at 2:25, Julian Tarkhanov wrote:

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

Right.
http://www.thekode.net/ruby/techniques/DefiningMethodsWithClo...
http://code.whytheluckystiff.net/metaid/browser/trunk...

--
Julian 'Julik' Tarkhanov
please send all personal mail to
me at julik.nl



mortee

10/2/2007 1:17:00 AM

0

Fefo

10/2/2007 2:06:00 AM

0

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 Tarkhanov wrote:
> On 2-okt-2007, at 2:25, Julian Tarkhanov wrote:
>
>> BTW, i'm also curious how you should use define_method with class
>> methods.
>
> Right.
> http://www.thekode.net/ruby/techniques/DefiningMethodsWithClo...
> http://code.whytheluckystiff.net/metaid/browser/trunk...

--
Posted via http://www.ruby-....

Robert Klemme

10/2/2007 9:10:00 AM

0

2007/10/2, mortee <mortee.lists@kavemalna.hu>:
> 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

Fefo

10/2/2007 2:19:00 PM

0


> 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
--
Posted via http://www.ruby-....

Robert Klemme

10/2/2007 2:30:00 PM

0

2007/10/2, Federico Brubacher <fbrubacher@gmail.com>:
>
> > 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 !?

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

mortee

10/2/2007 4:57:00 PM

0