Quick way to get attributes (without using collect)

This was inspired by Rails, but i think it’s common Ruby question. Say
i have an array of Employee objects, which have an attribute “name”. I
want to get all the names, as an array.

Let’s say i already have the variable “employees”, which points to an
array of Employee objects.

What i want to do is something like this -

employee_names = employees.names

But what i have to keep on doing is something like this -

employee_names = employees.collect{ |emp| emp.name}

Now, this isn’t a massive hassle, and i find Array#collect incredibly
useful, but it’s a bit annoying to have to keep typing it again and
again. Is there a way to set it up so that

.foos

is automatically treated as

.collect { |x| x.foo }

?

Or another way to do this simply?

thanks
max

On 20/11/2007, Max W. [email protected] wrote:

is automatically treated as

.collect { |x| x.foo }

?

Or another way to do this simply?

You can use the Symbol#to_proc method

class Symbol
def to_proc
Proc.new {|e| e.send(self)}
end
end

Which will let you right
employee_names = employees.collect(&:name)

Farrel

On Nov 20, 2007 6:03 PM, Max W. [email protected]
wrote:

is automatically treated as

.collect { |x| x.foo }

?

Or another way to do this simply?

Let me introduce to you #method_missing. It’s being called when the
method called is not found. The standard implementation just raises
NoMethodError,
but you can do anything there. In fact, this is how Rails does it’s
find_by_this_and_that magic.

So, this is the idea (not tested, just written in the mail):

class Array
def method_missing(name, *args)
single_method = name.to_s.chop # 1. name is a symbol, 2. chop for
simplicity
if !empty? && first.respond_to?(single_method)
collect { |x| x.send(single_method, *args) } # do whatever you
want here
else
super # inherited implementation
end
end
end

Just make sure that this will not break the whole system (check for
‘s’, and look any methods that might end with it, etc.)

Lookup the code in the Rails’ sources, most probably you’ll find there
nice singular/plural conversion etc.

Thanks guys - i’ll try the method missing approach, and also the rails
built in slightly shorter version :slight_smile:

cheers
max

On Nov 20, 12:12 pm, Farrel L. [email protected] wrote:

Which will let you right
employee_names = employees.collect(&:name)

map is shorter than collect. Also, adding optional args to to_proc
will make it more generally useful.

class Symbol
def to_proc
lambda {|obj, *args| obj.send(self, *args) }
end
end

employee_names = employees.map(&:name)

I’d be wary of using method_missing for this.

Brian A.

Max W. wrote:

This was inspired by Rails, but i think it’s common Ruby question.
[…]
What i want to do is something like this -

employee_names = employees.names

But what i have to keep on doing is something like this -

employee_names = employees.collect{ |emp| emp.name}

In rails you can do “employee_names = employees.collect(&:name)” which
is
somewhat more concise (though it incurs a performance penalty, I am
told).

Is there a way to set it up so that
.foos
is automatically treated as
.collect { |x| x.foo }

module Enumerable
def method_missing(meth, *args, &blk)
map {|x| x.send(meth,*args, &blk)}
end
end

This might easily lead to bugs though if you want to invoke method on
the
elements of the enum, not considering or not knowing that the enum
itself
implements that method as well. For example:
[2,4,6] / 2 #=> [1, 2, 3]
But:
[2,4,6] * 2 #=> [2,4,6,2,4,6]

Or:
[“1”,“2”,“3”].to_i #=> [1, 2, 3]
But:
[1,2,3].to_s #=> “123”

HTH,
Sebastian