I have a collection of Post objects and I want to be able to sort them based on these conditions:
Some posts will have dates (news and events), others will have explicit positions (labs, and portfolio).
I want to be able to call posts.sort!
, so I've overridden <=>
, but am looking for the most effective way of sorting by these conditions. Below is a pseudo method:
def <=>(other)
# first, everything is sorted into
# smaller chunks by category
self.category <=> other.category
# then, per category, by date or position
if self.date and other.date
self.date <=> other.date
else
self.position <=> other.position
end
end
It seems like I'd have to actually sort two separate times, rather than cramming everything into that one method. Something like sort_by_category
, then sort!
. What is the most ruby way to do this?
You should always sort by the same criteria to insure a meaningful order. If comparing two nil
dates, it is fine that the position
will judge of the order, but if comparing one nil
date with a set date, you have to decide which goes first, irrespective of the position (for example by mapping nil
to a day way in the past).
Otherwise imagine the following:
a.date = nil ; a.position = 1
b.date = Time.now - 1.day ; b.position = 2
c.date = Time.now ; c.position = 0
By your original criteria, you would have: a < b < c < a. So, which one is the smallest??
You also want to do the sort at once. For your <=>
implementation, use #nonzero?
:
def <=>(other)
return nil unless other.is_a?(Post)
(self.category <=> other.category).nonzero? ||
((self.date || AGES_AGO) <=> (other.date || AGES_AGO)).nonzero? ||
(self.position <=> other.position).nonzero? ||
0
end
If you use your comparison criteria just once, or if that criteria is not universal and thus don't want to define <=>
, you could use sort
with a block:
post_ary.sort{|a, b| (a.category <=> ...).non_zero? || ... }
Better still, there is sort_by
and sort_by!
which you can use to build an array for what to compare in which priority:
post_ary.sort_by{|a| [a.category, a.date || AGES_AGO, a.position] }
Besides being shorter, using sort_by
has the advantage that you can only obtain a well ordered criteria.
Notes:
sort_by!
was introduced in Ruby 1.9.2. You can require 'backports/1.9.2/array/sort_by'
to use it with older Rubies.Post
is not a subclass of ActiveRecord::Base
(in which case you'd want the sort to be done by the db server).