好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

rails3好文

rails3好文

http://asciicasts.com/episodes/202-active-record-queries-in-rails-3

 

Home   Other languages   Newest episode   All episodes   About ASCIIcasts Search:   

Tags performance caching active-record active-support views security refactoring testing controllers forms administration debugging tools e-commerce plugins ajax routing rails-23 rails-22 authentication models authorization rails-30 mailing authentications

202: Active Record Queries in Rails 3   (view original Railscast)

Other translations:       

Other formats:

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 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.

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

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 

查看更多关于rails3好文的详细内容...

  阅读:39次