163

I have an array of objects in Ruby on Rails. I want to sort the array by an attribute of the object. Is it possible?

10 Answers 10

273

I recommend using sort_by instead:

objects.sort_by {|obj| obj.attribute}

Especially if attribute may be calculated.

Or a more concise approach:

objects.sort_by(&:attribute)
Sign up to request clarification or add additional context in comments.

5 Comments

Or the shorthand version: objects.sort_by(&:attribute)
even more shorter objects.sort_by &:attribute
If you have problems with uppercase & lowercase letters being sorted separately, you can use objects.sort_by { |obj| obj.attribute.downcase }
Any idea how this compares with sort! (e.g. speed, etc.)?
38

Ascending order :

objects_array.sort! { |a, b|  a.attribute <=> b.attribute }

or

objects_array.sort_by{ |obj| obj.attribute }

Descending order :

objects_array.sort! { |a, b|  b.attribute <=> a.attribute }

or

objects_array.sort_by{ |obj| obj.attribute }.reverse

Comments

37

Yes, using Array#sort! this is easy.

myarray.sort! { |a, b|  a.attribute <=> b.attribute }

8 Comments

Thnx buddy but it didn't work out for me i have an array of objects. In which one of the attribute of the object is created_at. I want to sort it with this field. so i did @comm_bytes.sort! {|a, b| a.created_at <=> b.created_at } but no luck for me can u help....??
Is there a created_at method to access the @created_at attribute? What kind of object is @created_at? Does it define <=>? What kind of errors are you getting? etc, etc, ad nauseum. In other words, we need more detail than "but no luck for me".
it works if you do myarray = myarray.sort {...} without "!"
@Doru Yes, that works too, but why would you do that? It’s less direct and less efficient. Use sort if you want to preserve the original and assign the result to a different object; otherwise, use the in-place variant, sort!. In fact, sort calls sort! internally, after copying the original object.
@Doru There must be an error somewhere else in the code, I can 100% guarantee you that sort! will work just fine, and always (!) do the same thing as what you’ve written.
|
21

in case you need sorting by two attributes, where first one is more important then second (means taking in account second arguments only if first arguments are equal), then you may do like this

myarray.sort{ |a,b| (a.attr1 == b.attr1) ? a.attr2 <=> b.attr2 : a.attr1 <=> b.attr1 }

or in case of array of arrays

myarray.sort{ |a,b| (a[0] == b[0]) ? a[1] <=> b[1] : a[0] <=> b[0] }

Comments

19

You can make any class sortable by overriding the <=> method:

class Person

  attr_accessor :first_name, :last_name

  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end

  def <=>(other)
    @last_name + @first_name <=> other.last_name + other.first_name
  end

end

Now an array of Person objects will be sortable on last_name.

ar = [Person.new("Eric", "Cunningham"), Person.new("Homer", "Allen")]

puts ar  # => [ "Eric Cunningham", "Homer Allen"]  (Person objects!)

ar.sort!

puts ar  # => [ "Homer Allen", "Eric Cunningham" ]

1 Comment

This (overriding <=>) is what I was looking for, thanks. One suggestion, though: Instead of sorting by (abbreviating for space) last + first (with string concatenation), maybe sort by [last, first] (making an array -- and let ruby's sort work out the groupings under the covers). May not matter in most cases, but imagine the case of comparing "Stephen Mac", "Alice Mac", and "Billy MacGregor". With array-based sorting, the "Mac"s will stay together. With string-based, they get split up.
12

More elegant objects.sort_by(&:attribute), you can add on a .reverse if you need to switch the order.

Comments

10

Array#sort works well, as posted above:

myarray.sort! { |a, b|  a.attribute <=> b.attribute }

BUT, you need to make sure that the <=> operator is implemented for that attribute. If it's a Ruby native data type, this isn't a problem. Otherwise, write you own implementation that returns -1 if a < b, 0 if they are equal, and 1 if a > b.

Comments

1

I would like to add also, if your attribute can be nil:

If your attribute is String:

objects_array.sort_by{ |obj| [ obj.attribute ? obj.attribute : "param_by_default".to_s ] }

If your attribute is DateTime:

objects_array.sort_by{ |obj| [ obj.attr_time ? obj.attr_time : "06:00".to_datetime ] }

"06:00" - default datetime.

1 Comment

I like this suggestion, but I would recommend something like this, rather than having to provide a fallback value for nil... objects_array.sort_by do |obj| [obj.attribute.nil? ? 1 : 0, obj.attribute] end The 1 and 0 could be swapped if you prefer nil values first, rather than last.
-2

Yes its possible

http://ariejan.net/2007/01/28/ruby-sort-an-array-of-objects-by-an-attribute/

Comments

-2
@model_name.sort! { |a,b| a.attribute <=> b.attribute }

2 Comments

There are plenty of identical answers posted in 2009. No need to add another one.
Don't use sort when you are sorting objects that can't be directly compared. If you have to access attributes or do a computation to get the value to compare use sort_by. It will be much faster.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.