arunt
arunt

Reputation: 386

Rails sort_by on Array throwing ArgumentError because of nil values - shouldn't it just sort depending on database?

I am sorting an Array in Rails 4 using sort_by:

sort_column = 'experience_in_months'
@[email protected]_by(&:"#{sort_column}")

This throws an error

An ArgumentError occurred in SNIP:

comparison of Fixnum with nil failed

This is a strange problem because I remember reading about this earlier. It was supposed to put the nil values first or last based on the database used. I am using PostGreSQL.

I remember this working quite flawlessly a few days ago - it just put nil values at the top in my table.

It isn't a difficult problem to solve - I can take out the nil values, sort the rest and then add back the nil enteries. But I would like to be clear if this is a random behaviour or if it is as expected.

Upvotes: 0

Views: 1010

Answers (2)

D-side
D-side

Reputation: 9485

This is expected behavior

Database ordering only applies in database queries

Once the data is out of the database, Ruby has full control and ruling over ordering rules. Databases' rules no longer apply because arrays are used everywhere and no gems expect them to be broken in this way.

Taking advantage of the fact that nil.to_i is 0, you can use the following:

attribute_proc = sort_column.to_sym.to_proc
@userstoshow = @userstoshow.sort_by { |x| attribute_proc.call(x).to_i }

...and you're replacing the value in-place, so you can use sort_by!, not sort_by:

attribute_proc = sort_column.to_sym.to_proc
@userstoshow.sort_by! { |x| attribute_proc.call(x).to_i }

Looks kinda intimidating. That's because you're not using a fixed attribute name. Otherwise, it'd be:

@userstoshow.sort_by! { |x| x.experience_in_months.to_i }

Upvotes: 1

gmaliar
gmaliar

Reputation: 5479

@arunt, I am guessing that you have in @userstoshow objects of type User or something as such.

Now when you use Ruby's sort_by it enumerates over the collection and tries to a <=> b, here is an equivalent code snippet.

@userstoshow = @userstoshow.sort_by do |a, b|
  a.experience_in_months <=> b.experience_in_months
end

Now Ruby doesn't know how to compare a Fixnum to a NilClass, here is a similar case

[1, 2, 3, nil].sort => ArgumentError: comparison of Fixnum with nil failed

So the problem is that your User object is returning nil for some of it's object rather than a Fixnum value.

So you can either change your sort_by to include a code block or fix your User object to return a default value which is not nil.

Upvotes: 2

Related Questions