This post originated from an RSS feed registered with Ruby Buzz
by rwdaigle.
Original Post: Named Scope: It's Not Just for Conditions, Ya Know?
Feed Title: Ryan's Scraps
Feed URL: http://feeds.feedburner.com/RyansScraps
Feed Description: Ryan Daigle's various technically inclined rants along w/ the "What's new in Edge Rails" series.
Named scopes in Rails are great, everybody knows that. They’re usually used to create granular, chainable sets of SQL conditions that nicely encapsulate your domain query logic. Here’s a simple example:
123456789101112
classArticle < ActiveRecord::Base# Get all articles that have been published named_scope :published, :conditions => ['published = ?', true]# Get all articles that were created recently named_scope :recent, lambda { { :conditions => ['created_at >= ?', 1.week.ago] } }end# Get all recently created articles that have been publishedArticle.published.recent #=> [<Article id: ...>, <..>]
However, as much as I use named_scope for this purpose, I also use it for some smaller and still useful functions. For instance, I find that I often need to just fetch the first X number of results for any particular query. Instead of having to call find with the :limit option you could create the following named_scope:
123456789
classArticle < ActiveRecord::Base# Only get the first X results named_scope :limited, lambda { |num| { :limit => num } }end# Get the first 5 articles - instead of Article.find(:all, :limit => 5)Article.limited(5) #=> [<Article id: ...>, <..>]
Hey, any less typing I’ll take, and I find myself using this limited named_scope a lot. But let’s pimp it a little so that you don’t always have to supply the number, and make it default to the per_page value that exists on the class if you’re using will_paginate.
123456789101112131415161718
classArticle < ActiveRecord::Base# Only get the first X results. If no arg is given then try to# use the per_page value that will_paginate uses. If that# doesn't exist then use 10 named_scope :limited, lambda { |*num| { :limit => num.flatten.first || (defined?(per_page) ? per_page : 10) } }defper_page; 15; endend# Get the first 15 articlesArticle.limited #=> [<Article id: ...>, <..>]# Get the first 5 articlesArticle.limited(5) #=> [<Article id: ...>, <..>]
Note that we have to use the variable length *num argument in the lambda to allow for no arguments.
Cool, so we’ve got a handy little tool for our toolbox now. Here’s another one I find myself using that isn’t strictly a conditional scope – ordered:
123456789101112131415
classArticle < ActiveRecord::Base# Order the results by the given argument, or 'created_at DESC'# if no arg is given named_scope :ordered, lambda { |*order| { :order => order.flatten.first || 'created_at DESC' } }end# Get all articles ordered by 'created_at DESC'Article.ordered #=> [<Article id: ...>, <..>]# Get all articles ordered by 'updated_at DESC'Article.ordered('updated_at DESC') #=> [<Article id: ...>, <..>]
I’ve bundled these scopes up into a “utility scopes:http://github.com/yfactorial/utility_scopes” plugin/gem if you think they look useful to you. I’ve also added some class-level convenience initializers to let you override the default values (like the default limit and default order clause):
1234567891011121314151617181920
classArticle < ActiveRecord::Base# This class's default ordering (if not specified# defaults to 'created_at DESC' ordered_by 'published_at DESC'# By default, return 15 results (if not specified# defaults to 10 default_limit 15end# Get the first 15 articles ordered by 'published_at DESC'Article.ordered.limited #=> [<Article id: ...>, <..>]# Get the first 15 articles ordered by 'popularity ASC'Article.ordered('popularity ASC').limited #=> [<Article id: ...>, <..>]# Get the first 20 articles ordered by 'popularity ASC'Article.ordered('popularity ASC').limited(20) #=> [<Article id: ...>, <..>]
Need a little something else? How about the with scope which will eager load the specified associations:
1234567
classArticle < ActiveRecord::Base has_many :comments has_many :contributors, :class_name => 'User'end# Get the first 10 articles along with their comments, comment authors and article contributorsArticle.limit(10).with({ :comments => :author }, :contributors)
You can get all these goodies yourself by doing the following in your Rails 2.1 app. In config/environment.rb specify the gem dependency:
Independent of whether or not you find these scopes useful, remember that named_scope is all up in your queries’ bidness – not just your queries’ conditions
Have some utility scopes you find to be indispensable? Let me know here or send me a request on github (user is yfactorial).