5.12. Using named_scope

At this stage your The Rails Noob blog publishes every single article in the database regardless of its published or unpublished status. This is because in the index action you used a finder method, which retrieves all the records from the articles table:

@articles = Article.find(:all, :order => "published_at DESC")

You can change this in order to select only published articles by specifying a condition in the query:

@articles = Article.find(:all, :conditions => { :published => true }, :order =>
"published_at DESC")

This is converted into the SQL query:

SELECT * FROM "articles" WHERE ("articles"."published" = 't') ORDER BY published_at
DESC

You don't want to show all the published articles though. For example, if an article is set as published, but its publication date is in the future, that article is scheduled and should be shown only when its publication date is presently due or if it was in the past. You need to indicate two conditions to the find method, then, as shown here:

@articles = Article.find(:all, :conditions => ["published = ? AND published_at <=
?", true, Time.now], :order => "published_at DESC")

Notice how you specify the conditions by assigning an array to the :conditions key. This array contains the SQL condition, and the two parameters that the condition requires, in the order that they appear within it. The resulting SQL query will resemble the following:

SELECT * FROM "articles" WHERE (published = 't' AND published_at <= '2008-07-16
21:49:52') ORDER BY published_at DESC

This is almost what you want, except that there's a problem: Time.now is a local time and it gets compared to UTC times in the database. To fix this you need to convert it to UTC through the utc instance method:

@articles = Article.find(:all, :conditions => ["published = ? AND published_at <= ?", true,
Time.now.utc], :order => "published_at DESC")

This, at the time I ran the query, was converted into:

SELECT * FROM "articles" WHERE (published = 't' AND published_at <= '2008-07-17
02:01:22') ORDER BY published_at DESC

This is exactly the query you were aiming for. In theory you could have gone ahead and applied the new finder options in your index action and everything would work as expected. There is, however, a consideration to be made.

If you were to develop your blog in the real world, so as to include many typical blog features, it's very possible that you'd find yourself refining your query further in different ways. For example, in an action you may want to retrieve a list of published articles for a given month, whereas in another you may wish to get a list of articles that have been published within a certain range of dates. All these finders, which can become visually quite busy, will have a lot of common code because they all need to retrieve a list of published posts.

One approach to deal with this repetition, and adhere to the DRY principle, would be to create a class method in the model as follows:

# Returns published articles excluding scheduled ones
def self.published(options)
    find(:all, options.merge(:conditions => ["published = ? AND published_at <= ?",
true, Time.now.utc]))
end

This solution enables you to call Article.published as well as pass a few finder options to the method from your controller:

Article.published(:order => "published_at DESC")

Since version 2.1, Rails offers an even better solution that is more concise and far more flexible: named scopes. These were introduced into Rails' core with the 2.1 release, but were already available before thanks to a plugin called has_finder. The idea behind named scopes is that it's possible to attribute a name to a customized finder much as you would with a class method. Unlike a class method though, these can be chained with other ActiveRecord methods so that they become a very easy way to reuse the finder logic that they encapsulate. Within your models you can define a named scope with the method named_scope. Go ahead and add the following named scopes to the Article model (in appmodelsarticle.rb):

# Finds all the published posts excluding scheduled ones
named_scope :published, :conditions => ["published = ? AND published_at <= ?",
true, Time.now.utc]

# Finds all the drafts and scheduled posts
named_scope :unpublished, :conditions => ["published = ? OR published_at > ?",
false, Time.now.utc]

Now you'll be able to run Article.published and obtain a list of published (excluding scheduled ones) articles. Likewise, you can use Article.unpublished to get an array of scheduled articles and drafts. You can also transparently chain a finder method to it (or any Article class method or Array instance method) to refine the select conditions further.

Change the finder in the index action within the Articles controller from:

@articles = Article.find(:all, :order => "published_at DESC")

to:

@articles = Article.published.find(:all, :order => "published_at DESC")

Reload http://localhost:3000 and you should see that only published, non-scheduled articles appear.

Feel free to change the publication status or scheduled dates of a few articles of your own to see how this is properly reflected on the articles (as shown on the blog's homepage). You can also use the Rails console introduced previously, which is also a quick way to modify the status of a record as shown in the following Rails console session:

C:projectslog> ruby script/console
Loading development environment (Rails 2.2.2)
>> a = Article.published.first
=> #<Article id: 1, title: "Hello, Rails!", body: "Hi from the body of an articl
e. :)", published: true, published_at: "2008-07-11 09:24:00", created_at: "2008-
07-11 09:32:41", updated_at: "2008-07-11 09:32:41">
>> a.published
=> true
>> a.published = false
=> false
>> a.save
=> true
>> a
=> #<Article id: 1, title: "Hello, Rails!", body: "Hi from the body of an articl

e. :)", published: false, published_at: "2008-07-11 09:24:00", created_at: "2008
-07-11 09:32:41", updated_at: "2008-07-17 03:18:28">

Like irb, enter exit and hit the Return key to quit the console.

Notice that there is no collision between the named scope published and the attribute published. The first is accessed from the Article class (like a common class method), whereas the second is only defined by Article instances.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.151.44