Rails 2.1 allows you to add something called “named scope” (formerly a plugin) to your records. This allows you to more easily classify and access records. Let’s look at some examples.
In Launchpad, one of the main classes we have is the Article class (to represent a written article / blog-post type object). One distinction we need to make in almost every query is the published status; we need to do things like this:
@newest = Article.find(:first, :conditions => ["is_published = ?", true], :limit => 10)
Wouldn’t it be easier if we could just access all the published articles as a separate collection? This is precisely what named scope allows us to accomplish.
To create a collection of published articles via named scope, we can do this:
class Article
named_scope :published, :conditions => {:is_published => true}
# ...
end
This will allow us to access all the published articles through Article.published.
Another distinction we need to make often is articles that are published before NOW. This is because we allow putting a future date on articles, and displaying those when that date/time arrives.
We’d create another named scope like so:
named_scope :visible_now, :conditions => {:created_on <= Date.today}
What if we want to combine these two together to get all published articles visible now? Do we need to make a new named scope?
No! Simply chain them, like so:
@recent = Article.visible_now.published
... and it works!
One final issue we'll address is parameterizable named scopes. Authors who log in want to see all their articles (regardless of publication date/time/status); it's impossible to create one named scope per author (and even if it was possible, it would be impractical); can we still somehow take advantage of named scopes to solve this problem?
We can--because named scopes can take an optional block of code/variables. We can create a parameterizable scope that takes the author ID, like so:
named_scope :my_articles, lambda { |my_id|
{ :conditions => ["author_id = ?", my_id] }
}
Then, to get the articles for the current user, we just pass in the right ID, like so:
@current_author_articles = Article.my_articles(current_user.id)
And Rails conveniently provides the requested data, thanks to our handy lambda function. (Actually, this example is a bit contrived; we know the ID is current_user.id, which is available in the model, so there's no point passing it in as a parameter.)
Testing named scopes is a bit trickier; for that, consult the Rails 2.1 ebook.
I don’t think the
named_scope :visible_now, :conditions => {:created_on {:created_on <= Date.today}}}
I mean, the way you coded the :visible_now scope, the Date.now will be evaluated when the server starts, not when the query is run. So your query will give increasingly wrong answers as time goes on :-) The right way to do it is to wrap the condition in a lambda; they are not just for capturing parameters, they’re also for delayed execution.
Oh nice, I wasn’t aware of that. Thanks for the correction … I didn’t know that it’s evaluated at server start-time. Did you confirm this empirically? :)
ashes – models only get loaded once – when the server starts. As a result, any code that isn’t run at runtime (this includes behaviours, relationships/associations, scopes.etc.) only get evaluated once the server has started.