Why does this work?

Hi. I’m still getting adjusted to Rails coding, and and stumbled into a
piece of code that in my mind, shouldn’t work, but it does. I know Rails
is smart, but…take a look.

I’m trying to populate a select box with first and last names, which are
stored in separate fields: employee.lname and employee.fname.

@employees = Employee.find(:all, :order => “name”)

I wanted the box to display “lname, fname” and tried to use the
collection_select method to easily assign :id to each option value. I
assumed using collection_select wouldn’t work because it only lets me
specify one field for option text. So I tried making a new hash with a
formatted string:

@users = Array.new
@employees.each do |employee|
@users.push([employee.id => employee.lname << ", " << employee.fname])
end

And in my template, I have:

<%= collection_select(:profile, :user, @employees, :id, :lname) %>

And lo and behold, I get a text box with each name formatted like
“Smith, John.” But I don’t see how its possible, since in my
collection_select statement, I reference the original @employees hash
and not @users. I also only specify :lname as a parameter. But if I take
out the each loop, it goes back to only listing the last name. What
gives? The only explanation I can think of is if the @employees.each
loop is modifying @employees hash itself.

Your suspicion is correct. “<<” is a “destructive” operator, so your:

@users.push([employee.id => employee.lname << ", " << employee.fname])

is actually pushing “,” and employee.fname INTO employee.lname,
destroying its original value.

On 10/2/06, Bryan M [email protected] wrote:

I wanted the box to display “lname, fname” and tried to use the
And in my template, I have:


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


[The Test of Negation] Don’t include a sentence in documentation if its
negation is obviously false. – Bob Martin

Bryan M wrote:

“Smith, John.” But I don’t see how its possible, since in my
collection_select statement, I reference the original @employees hash
and not @users. I also only specify :lname as a parameter. But if I take
out the each loop, it goes back to only listing the last name. What
gives? The only explanation I can think of is if the @employees.each
loop is modifying @employees hash itself.

It is. The ‘<<’ method of String doesn’t just return the new
concatenated string, it actually modifies the original string as well.

So if I have

a = “Hello”
a << " world"

then a will now contain “Hello world”.

In your iterator, you’re actually adding ", " and employee.fname to
employee.lname for each employee. Good job you weren’t calling
employee.save anywhere in there!

If you want to concatenate strings without this side effect, you could
do something like:

@users.push(employee.id => “#{employee.lname}, #{employee.fname}”)

Chris

Chris M. wrote:

Bryan M wrote:

@users = Array.new
@employees.each do |employee|
@users.push([employee.id => employee.lname << ", " << employee.fname])
end

If you want to concatenate strings without this side effect, you could
do something like:

@users.push(employee.id => “#{employee.lname}, #{employee.fname}”)

Of course, if you want to be really Rubyish, you could replace that
whole lot with:

@users = @employees.collect {|e| [e.id, “#{employee.lname},
#{employee.fname}”]}

Chris

Ah. Thanks for all your (really quick) replies. That certainly makes
sense. I didn’t really read up on concat operators before choosing <<.
So it was kind of a happy accident, but I’ll definately follow your
suggestions to make it less destructive.

Bryan