Tuesday
25Nov2008
Sorting ActiveRecord Objects
Tuesday, November 25, 2008 at 3:09PM
I recently hit a situation where I needed to sort an array of ActiveRecord objects on a particular attribute. The catch was that in this case the array started out with the results of a find operation - but then it had a bunch more transient objects added to it that weren't part of the database. Fortunately the Array#sort method makes short work of this. Given an array
[sourcecode language='ruby']
a.sort! {|x,y| x.entry_date <=> y.entry_date}
[/sourcecode]
Because this was the only sort I needed on this particular model, I decided to push the operation right down into the model:
[sourcecode language='ruby']
class Receipt < ActiveRecord::Base
def <=> (other)
entry_date <=> other.entry_date
end
end
[/sourcecode]
Then the sort is much simpler:
[sourcecode language='ruby']
a.sort!
[/sourcecode]
Note that this technique only makes sense if your array isn't coming straight from the database. If you are retrieving records from the database, you're better off including an
a of objects with an entry_date attribute:[sourcecode language='ruby']
a.sort! {|x,y| x.entry_date <=> y.entry_date}
[/sourcecode]
Because this was the only sort I needed on this particular model, I decided to push the operation right down into the model:
[sourcecode language='ruby']
class Receipt < ActiveRecord::Base
def <=> (other)
entry_date <=> other.entry_date
end
end
[/sourcecode]
Then the sort is much simpler:
[sourcecode language='ruby']
a.sort!
[/sourcecode]
Note that this technique only makes sense if your array isn't coming straight from the database. If you are retrieving records from the database, you're better off including an
:order clause in your finder to let the database do the sorting.

Reader Comments (2)
I would rather use #sort_by (if making another array is ok) or something like this:
a.sort! &Comparator.of(:entry_date)
with
class Comparator < Proc
def self.of(*attributes)
new(*attributes)
end
def initialize(*attributes)
@attributes = attributes
@direction_modifier = 1
end
def call(*args)
# handle invalid args.size
a, b = args[0], args[1]
@attributes.each do |attr|
result = a.send(attr) b.send(attr)
return result*direction_modifier if result != 0
end
return 0
end
def reversed
@direction_modifier = -@direction_modifier
end
end
This will allow comparing multiple fields, like
Comparator.of(:lastname, :firstname)
or
Comparator.of(:lastname, :firstname).reversed
Or you could do Receipt.find(:all).sort_by(&:entry_date)