Rails 2.3 Makes Finders Fancier

Checking out improvements to Rails' ActiveRecord object, including dynamic scopes and batches.

Rails 2.3 was released two weeks ago and the list of additions and refinements is expansive. Some of the changes I’m enamored of include nested attributes and the complementary nested object forms, integration of Rails Metal, and official support of application templates.

  • Nested attributes and nested object forms simplify all the code associated with a form that contains more than one model. Validation occurs en masse in the model; a form can nest models to any depth; and the controller requires no additional code to process nested objects.
  • Rails Metal is aptly named since its intent is to speedily process a request against “bare metal”, or without the incumbent Rails overhead. Metal is ideal for AJAX and APIs, where you typically don’t need to fuss much with the request — just let the endpoint interpret the incoming URL and return some data.
  • An application template is a boilerplate for a new Rails application. Define what plug-ins you want to appear in new applications and from then on, the rails command includes those automatically. Better yet, you can apply an application template to an existing application with a new rake task. No more re-generating the wheel!

I’ll write extensively on each of these topics in the immediate future. Here, I want to focus on some smaller-scale improvements to ActiveRecord that will likely improve more of your Rails code.

A Little Background on Scope

Rails 2.1 introduced named scopes, essentially a shorthand for a set of find conditions. For example, here is a simple named scope named active to find all accounts where the active field (a flag) is true.

class Account < ActiveRecord::Base
  named_scope :active, :conditions => { :active => true }
end

Once defined, a named scope can be used exactly as you might use Account.all or Account.first (which are also scopes). For example, this code iterates over all active accounts:

Acccount.active.each do |account|
 ...
end

The shorthand is convenient to write and easy to read, but the real power of named scopes lay in chaining more than one together.

For instance, if I create another named_scope called platinum to yield those accounts that pay for the highest tier of services, I can find all active platinum accounts with the natural statement @accounts = Account.active.platinum.

When I read “scope”, I think filter. A scope constrains results to some specification. A chain of named scopes is an increasingly narrow filter.

Rails 2.3 adds a number of other features akin to named scopes to simplify find. A model may now have a default scope. Rails now supports dynamic scopes. And, to ease demands on memory, you can now find records in batches. Let’s look briefly at each feature.

Default Scope

A default scope, as you might guess from its name, defines a default set of find conditions for a model. Continuing the accounts example from above, if I want to always enumerate accounts sorted by name, I’d add the bolded line to my Account model:

class Account < ActiveRecord::Base
  default_scope :order => 'name ASC'
end

Now, unless I override the :order condition, the results of every find are sorted by name in ascending order. Indeed, even the result of Account.active.platinum are affected by this default scope.

Dynamic Scope

Next, is the dynamic scope, a hybrid of a dynamic finder method and a named scope. Again, you’ve likely used a dynamic finder to look up data. For instance, if the Account model has a field tier, you could write Account.find_by_tier('platinum') to find all accounts with platinum status. find_by_tier() is not defined anywhere—Rails realizes it for you as needed.

A dynamic scope is realized on the fly, too. And like a named scope, it filters results. Thus, I can find active gold status accounts by writing this:

Account.scoped_by_active(true).scoped_by_tier('gold')

This statement is the equivalent of…

Account.find(:all, :conditions => { :active => true, :tier => 'gold' })

… but a lot more friendly to read.

Batches

The last convenience to mention doesn’t save typing, but it does save memory. You can now retrieve records in batches, ideal if you have a great deal of records in a table. The new methods are find_in_batches() and find_each. The former collects 1,000 records at a time, or some other amount if you specify :batch_size. The latter returns a record at a time (but internally, this uses find_in_batches() for efficiency).

To process all active accounts in large batches, you can write:

Account.find_in_batches(:conditions => {:active => true}) do |accounts|
  accounts.each { |account| account.renew_subscription }
end

You cannot use :limit nor :order with find_in_batches(). Most of the other find options are acceptable, though.

Use of find_each() is similar.

Account.find_each(:conditions => {:active => true}) do |account|
  account.renew_subscription
end

Finding My Way

As I said, these new find features are relatively small, incremental improvements, but I find these much easier and natural to express what I am trying to find. I can think of several places in my own code that can benefit from the clarity. Lord knows I need the help!

In coming weeks I’ll present more about Rails 2.3 and also wander all over the World Wide Web to find tools, techniques, and tinkering to entertain you.

Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linux-mag.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62