Pratik has a very thorough and well written post about the upcoming changes to the ActiveRecord finder methods in Rails 3.1 and 3.2.

Specifically, the current options parameter is going to be removed in favor of method chaining.

For example,

Car.all(:conditions=>{:color=>'black'}, :order=>'cars.price DESC', :limit=>10)

will be replaced by:

Car.where(:color => 'black').order('cars.price DESC').limit(10)

While I do think the new syntax looks nice, my first thought was “how much time is this going to take to upgrade our app”

$ grep ":conditions" app lib vendor/gems vendor/plugins | wc -l
350

Okay, so that pretty much means we’re not moving to the new syntax any time soon. We obviously rely heavily on the exisiting :conditions syntax. There will be an officially supported plugin to keep the old-style, and I applaud the core team for doing that.

We have become quite fond of building up an array of options to pass to finder methods. This idiom works incredibly well when deal with user-defined queries. Consider an example where a user can query for their social graph.


Class User
  has_many :relationships
end

Now consider a web interface to query for friends. The user can sort by name (the default) or age, can choose to see 10, 20, or all of them, and can choose to only see men, women or both. Here’s how I would code that up (ignoring sanitization for now)*:

options={}
options[:order]=params[:order] if params[:order]
options[:limit]=params[:limit] if params[:limit]
options[:conditions]={:gender=>params[:gender]} if params[:gender]
user.relationships.all options

How would you do that with the new syntax? Well, instead of building up an array of options, you would need to conditionally pass messages:

relationships = user.relationships
relationships = relationships.limit(params[:limit]) if params[:limit]
relationships = relationships.order(params[:order]) if params[:order]
relationships = relationships.where(:gender=>params[:gender] ) if params[:gender]
relationships.all

They are both about the same amount of code, and will generate the SQL under the hood. But the latter requires you understand the way ActiveRecord lazy loads associations and handles message. In this case, I much prefer the former; building up options and passing them to a finder is a much simpler concept.

* NB You can’t simply do this:

user.relationships.limit(params[:limit]).order(params[:order]).where(:gender=>params[:where])

because all the params are optional. As designed, these queries will use the default values from the has_many association. Passing in :gender=>nil is very different than the default.

Monday, January 25, 2010