homeASCIIcasts

202: Active Record Queries in Rails 3 

(view original Railscast)

Other translations: Cn De Es It

Over the last two episodes we’ve shown you how to set up your computer for Rails 3 and create new Rails 3 applications. In this episode we’ll begin looking at some of its new features, starting with ActiveRecord which provides a new interface for performing database queries. Pratik Naik went into this subject in detail in a post on his blog recently which is well worth reading.

Some Basic Examples

To start we’ll show you a few examples of old ActiveRecord find calls and convert them into the new query format. For this we’ll be using a basic Rails application that has two models: Article and Comment that have a relationship whereby an Article has_many :comments.

The first find we’ll update returns the ten most recently published articles.

Article.find(:all, :order => "published_at desc", :limit => 10)

The basic approach to converting an ActiveRecord query to the new Rails 3 format is to look at the hash of options that’s being passed to find and to replace each item in the hash with an equivalent method. So, instead of the find call above we can use:

Article.order("published_at desc").limit(10)

As you can see the new syntax is easy to convert from the old Rails find but has a neater syntax.

The old hash options don’t always map exactly onto the new methods however as we’ll demonstrate in this next example.

Article.find(:all, :conditions => ["published_at <= ?", Time.now], :include => :comments)

There are only two real exceptions to the rule and conveniently the example above uses them both. The find above will get all of the articles that have a published date before the current time along with any associated comments. In Rails 3 this becomes:

Article.where("published_at <= ?", Time.now).includes(:comments)

Instead of :conditions we now use the where method, passing in the same arguments as we would to :conditions. The arguments can be passed as an array but it’s cleaner to pass them separately. For getting associated records :include gets pluralized to become the includes method. All of the other options we’d normally pass to find become methods with the same name as the option.

Our final example fetches the most recently published article.

Article.find(:first, :order => "published_at desc")

Using the Rails 3 syntax this becomes:

Article.order("published_at desc").first()

Note that we don’t call first until the end of the method chain.

As we’re fetching in descending order we could rewrite the line above as:

Article.order("published_at").last()

This will perform the same query but with slightly more concise code.

In Rails 3.0 we can use either the old find methods or the new Rails 3 syntax but in Rails 3.1 the old methods will be deprecated and from Rails 3.2 they will be removed completely. It’s well worth rewriting your finds as you migrate your applications to Rails 3 so that your applications will be compatible with future releases of Rails 3.

You might be wondering at this point just what the point of this new syntax is, especially as it will break a lot of existing Rails applications when they are upgraded. Well there is a purpose to this change and it lies in the power of lazy loading.

Lazy Loading

To demonstrate lazy loading we’ll use our application’s console. If we ask for all of the articles we’ll get an array returned as we’d expect and we’ll see that we have three articles in our database.

ruby-1.9.1-p378 > Article.all => [#<Article id: 1, name: "It's Ancient", published_at: nil, hidden: false, 
created_at: "2010-02-22 20:35:42", updated_at: "2010-02-22 20:35:42">, 
#<Article id: 2, name: "Can't See Me", published_at: nil, hidden: false, 
created_at: "2010-02-22 20:37:03", updated_at: "2010-02-22 20:37:03">, 
#<Article id: 3, name: "To the Future!", published_at: nil, hidden: false, 
created_at: "2010-02-22 20:38:17", updated_at: "2010-02-22 20:38:17">]

If we want to get all of the articles in alphabetical order we can do so by using the order method:

ruby-1.9.1-p378 > articles = Article.order("name")
 => #<ActiveRecord::Relation:0x00000101669b90 @table=#<Arel::Table:0x000001023e9af8 
@name="articles", @options={:engine=>#<Arel::Sql::Engine:0x000001023a15c8 @ar=ActiveRecord::Base,
@adapter_name="SQLite">}, @engine=#<Arel::Sql::Engine:0x000001023a15c8 @ar=ActiveRecord::Base,
@adapter_name="SQLite">, …

Instead of a list of articles being returned this time we have an ActiveRecord::Relation object. This object stores information about our find, but the database query hasn’t yet been made. This is what is meant by lazy loading in this context: the data isn’t loaded until it has to be. If we were to enumerate through the records with each or get all or just the first of the articles then the query will be made.

ruby-1.9.1-p378 > articles.first
 => #<Article id: 2, name: "Can't See Me", published_at: nil, hidden: false, 
created_at: "2010-02-22 20:37:03", updated_at: "2010-02-22 20:37:03">

Now let’s see how this applies to our application. We’ve generated a scaffold for the article model so we have an articles controller with the usual seven actions. The code for the index action uses Article.all to get all of the articles immediately:

/app/controllers/articles_controller.rb
def index
  @articles = Article.all
  respond_to do |format|
    format.html # index.html.erb
    format.xml  { render :xml => @articles }
  end
end

If we use use any of the find options on the articles, such as ordering by name then an ActiveRecord::Relation object will be returned instead and the query will not be performed in the controller. The database query will instead be performed in the view code when we enumerate through each Article.

/app/views/articles/index.html.erb
<% @articles.each do |article| %>
  <tr>
    <td><%= article.name %></td>
    <td><%= article.published_at %></td>
    <td><%= article.hidden %></td>
    <td><%= link_to 'Show', article %></td>
    <td><%= link_to 'Edit', edit_article_path(article) %></td>
    <td><%= link_to 'Destroy', article, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
<% end %>

If we load the index page now the articles will be shown in alphabetical order.

The scaffold-generated article index page.

The nice thing about this is that if you’re using fragment caching with the cache method in your view this will now work better as the database query will not be performed unless it is necessary.

The new query syntax makes it easier to build up find conditions. Let’s say we want to filter the articles so that only the hidden ones are shown if we have hidden=1 in the URL’s query string. We can do that by modifying the index action like this:

/app/controllers/articles_controller.rb
def index
  @articles = Article.order('name')
  if params[:hidden]
    @articles = @articles.where(:hidden =>(params[:hidden] == "1"))
  end
  respond_to do |format|
    format.html # index.html.erb
    format.xml  { render :xml => @articles }
  end
end

Now we check that the there is a hidden parameter passed and if there is we add a where method to the find that will show only the hidden articles if that hidden parameter has a value of 1. If we append that parameter to the URL and reload the page we’ll see just the hidden articles.

Showing just the hidden articles.

Likewise if we pass 0 we’ll see only the visible articles.

Now only the visible articles are shown.

Being able to chain together methods like this is a nice way to be able to build up more complex database queries while knowing that the query won’t actually be executed until the data is needed.

Named Scopes

Next we’ll show you some of the changes to named scopes in Rails 3. Below is our Article model with two named scopes, one to fetch the visible articles and one to fetch the articles that have been published.

/app/models/article.rb
class Article < ActiveRecord::Base
  named_scope :visible, :conditions => ["hidden != ?", true]
  named_scope :published, lambda { {:conditions => ["published_at <= ?", Time.zone.now]} }
end

These named scopes are defined as we’d define them in a Rails 2 application but the Rails 3 approach is a little different. The first difference is that the method to define a named scope is no longer named_scope but just scope. Also we no longer pass the conditions as a hash but, as with find, we use now use methods. Like we did with the new find methods we use where instead of :conditions. In the Rails 3 syntax the named scopes will look like this:

/app/models/article.rb
class Article < ActiveRecord::Base
  scope :visible, where("hidden != ?", true)
  scope :published, lambda { where("published_at <= ?", Time.zone.now) }
end

Another new feature is the ability to build up scopes. If we want to create a scope called recent that will return the recently published visible articles ordered by their publish date we can do so by reusing the two scopes we already have.

scope :recent, visible.published.order("published_at desc")

What we’ve done in our new scope is chain together the two scopes we already have an add an order method to create a new scope and this chaining ability is a very powerful feature to have when creating scopes for our models.

We can try our new named scope in the console. If we call Article.recent an ActiveRecord::NamedScope::Scope object is returned. This is an object that behaves in a similar way to the ActiveRecord::Relation object we saw earlier.

ruby-1.9.1-p378 > Article.recent
 => #<ActiveRecord::NamedScope::Scope:0x0000010318bd08 @table=#<Arel::Table:0x00000102740ea8 
 @name="articles", @options={:engine=>#<Arel::Sql::Engine:0x00000102651900 @ar=ActiveRecord::Base>}, 
 @engine=#<Arel::Sql::Engine:0x00000102651900 @ar=ActiveRecord::Base>>, …

If we call all on the Scope object then we’ll see the matching article returned.

ruby-1.9.1-p378 > Article.recent.all
 => [#<Article id: 1, name: "It's Ancient", published_at: "2010-01-01", 
 hidden: false, created_at: "2010-02-22 20:35:42", updated_at: "2010-02-22 23:00:16">]

A Final Tip

We’ll round up this episode with a useful tip. If you have a Relation or Scope object and want to see the SQL query that it would run against the database you can call to_sql on it.

ruby-1.9.1-p378 > Article.recent.to_sql
 => "SELECT     \"articles\".* FROM       \"articles\" 
 WHERE     (hidden != 't') AND (published_at <= '2010-02-22 22:47:12.023289') 
 ORDER BY  published_at desc"

This shows the SQL that ActiveRecord will perform to return the recently published articles that are not hidden.

That’s it for this episode on using ActiveRecord queries in Rails 3. There are a lot of great additions that will make the code in your Rails controllers more concise and expressive. Any time spent playing with the new features will soon repay itself.