Custom Sorting in Ruby
Dr. B. | Wednesday, January 17, 2007
I have been an expert Java developer for many years, but over the past year I've had the opportunity to use Ruby on Rails for a couple real projects. I'm really enjoying Ruby and hope to to use it more often in the future.
My current RoR project has several models with bi-directional
These relationships are illustrated (with simplication) here:
I needed to be able to list all the albums chosen by a user, sorted by album name. I tried this:
but the album names were not sorted. I thought that the nifty option
When you sort an Array (or anything Enumerable), you can override the
Then, just change the block slightly to use the sorted version:
If the model you're trying to sort is not already Comparable, you should add the line
Have fun writing your own custom sorting rules in Ruby! Leave a comment if you need more details.
Doug Smith, Senior Developer, Barefoot
My current RoR project has several models with bi-directional
has_many :through
relationships where two models link to a third join model that has one or more additional property fields. For example, Users link to Albums through a Selections join model. The selections table includes fields for user_id and album_id, with an additional field that contains the rating each user gives to each album. (This example uses different entity names to protect the client.) These relationships are illustrated (with simplication) here:
class User < ActiveRecord::Base
has_many :selections, :dependent => :destroy
has_many :albums, :through => :selections
end
class Album < ActiveRecord::Base
has_many :selections, :dependent => :destroy
has_many :users, :through => :selections
end
class Selection < ActiveRecord::Base
belongs_to :user
belongs_to :album
end
I needed to be able to list all the albums chosen by a user, sorted by album name. I tried this:
@user.selections.each do |selection|
# ...
end
but the album names were not sorted. I thought that the nifty option
:order => 'name'
would work on the has_many :through
, but alas, it didn't. Then, my Java experience reminded me of the Comparable interface. It turns out that Ruby includes a very similar pattern, but it comes complete with Ruby nice-ness.When you sort an Array (or anything Enumerable), you can override the
<=>
method of the Object class to provide your own custom sorting code. (Just like the equals() method in Java.) The <=>
method returns -1, 0, or 1 to indicate whether the instance is smaller, equal, or greater than the other object. Here's the method I added to the Selection model to tell it how to sort:
def <=>(o)
# Compare album name
album_name_cmp = self.album.name <=> o.album.name
return album_name_cmp unless album_name_cmp == 0
# Compare user last name
user_ln_cmp = self.user.last_name <=> o.user.last_name
return user_ln_cmp unless user_ln_cmp == 0
# Compare user first name
user_fn_cmp = self.user.first_name <=> o.user.first_name
return user_fn_cmp unless user_fn_cmp == 0
# Otherwise, compare IDs
return self.id <=> o.id
end
Then, just change the block slightly to use the sorted version:
@user.selections.sort.each do |selection|
# ...
end
If the model you're trying to sort is not already Comparable, you should add the line
include Comparable
to include the Comparable mixin. ActiveRecord objects are already Comparable. Have fun writing your own custom sorting rules in Ruby! Leave a comment if you need more details.
Doug Smith, Senior Developer, Barefoot
Labels: development, rails, ruby
6 comments
This comment has been removed by the author.
*tries again
You may be interested in reading this: Sortable has_many :through
Can't you just put:
has_many :selections, :dependent => :destroy, :include => :album
And then you should be able to use the ":order=>" option, or you can even put ":order =>" in your has_many definition.
@spint: your suggestion would work if I didn't have extra properties in the join table. I actually need the association of the middle table in order to work with its extra fields (i.e., each selection joins a user and an album and includes each user's rating of that album).
The :order => option didn't work for me in these kind of associations, so that's why I resorted to using Ruby's custom sorting API.
very useful article. thanks
very much for this article. It was exactly what I was looking for and worked like a charm!
Post a Comment
« Home