Rails – One sort to rule them all

[Reading time: 2mn]

Introduction
This post is a follow-up of the previous one hereĀ Rails – How to sort a model on a virtual attribute.

Setting
Ruby 1.9.2-p180
Rails 3.0.x

The Problem
So you have a Product model with a couple of virtual attributes like user rating which depends on some clever calculation. But you also have some real attributes.
And you’d like to not duplicate methods because:

  • sorting on “real” or database attributes will have you declare scopes. Okay, you can parameterize them to reduce the clutter but still.
  • sorting on virtual attributes will have you declare 2 sort class functions per attribute as detailed in the postĀ Rails – How to sort a model on a virtual attribute. Okay, you can parameterize them to have just one per attribute but still.

The Solution
Using some of Ruby’s metadata features, you can call the sort_by function on real atttributes using read_attribute and on virtual attribute using method(“blah”).call.
Here’s an example:

def self.sorted_by(field_name, direction = 1)
  p = Product.all[0]
  array = []
  if (p.read_attribute(field_name).nil?)
    # virtual attribute are methods
    array = Product.all.sort_by {|p| p.method(field_name).call}
  else
    array = Product.all.sort_by {|p| p.read_attribute(field_name)}
  end
  if (direction == -1)
    array.reverse
  else
    array
  end
end

Use with Caution

Please do note that the main drawback is performance. Databases are good at sorting (provided you have declared the right indexes) but here, we don’t use them. We use Ruby and it will obviously not be a good fit if you have thousands of rows in your tables.

 

Leave a Reply

Your email address will not be published. Required fields are marked *