Rails 2.x Named Scope
我在实际项目中发现 named_scope 的好处,下面翻译了 What’s New in Edge Rails: Has Finder Functionality 这篇文章,同时结合自己使用的心得与 Rails 爱好者们共享。
Rails 2.x 整合了 Nick Kallen 的 has_finder plugin,改名为 named_scope。首先,在 User Model 中定义 named_scope:
class User < ActiveRecord::Base
named_scope :active, :conditions => {:active => true}
named_scope :inactive, :conditions => {:active => false}
named_scope :recent, lambda { { :conditions => ['created_at > ?', 1.week.ago] } }
end
标准的使用:
User.active # 相当于 User.find(:all, :conditions => {:active => true})
User.inactive # 相当于 User.find(:all, :conditions => {:active => false})
User.recent # 相当于 User.find(:all, :conditions => ['created_at > ?', 1.week.ago])
不同的 named_scope 之间可以嵌套使用:
User.active.recent
这相当于下面的用法:
# User.with_scope(:conditions => {:active => true}) do
# User.find(:all, :conditions => ['created_at > ?', 1.week.ago])
# end
高级使用:
named_scope 允许在执行时,传递参数来定义查询条件。
class User < ActiveRecord::Base
named_scope :registered, lambda { |time_ago| { :conditions => ['created_at > ?', time_ago] }
end
User.registered 7.days.ago # 相当于 User.find(:all, :conditions => [‘created_at > ?’, 7.days.ago])
named_scope 扩展
与 association extensions 相似, named_scope 也可以扩展。
class User < ActiveRecord::Base
named_scope :inactive, :conditions => {:active => false} do
def activate
each { |i| i.update_attribute(:active, true) }
end
end
end
# 重新激活所有未激活用户
User.inactive.activate
匿名 Scopes
通过对类对象 class objects 使用 scoped 方法,你可以快速地建立 scope。
# 定义 named scopes
active = User.scoped(:conditions => {:active => true})
recent = User.scoped(:conditions => ['created_at > ?', 7.days.ago])
# 嵌套使用
recent_active = recent.active
# 对返回的对象进行操作
recent_active.each { |u| ... }
=============================================================================
单纯的 scope ,例如 User.active, User.inactive 等只是提供简单的快捷方法,并不具备强大的功能特性。我们在实际项目中,很多时候是得益于 scope 的嵌套使用。我认为嵌套使用主要有五个好处:
1. scope 的嵌套使用,能够多层次(只要符合逻辑,几乎为无限)的组合查询条件。例如User.active.recent, User.active.recent.female, User.active.recent.female.adult……
2. 能够与 association 结合使用,限定外部 scope。举个例子,假如 User Belongs To Club,你可以将查询限制到具体一个 Club 的范围,如 Club.first.users.active.recent。
3. 用面向对象的方式,使得复杂查询在一个 SQL 语句中完成,减少编写复杂 SQL 语句的次数。例如 User.active.recent.female.adult,该查询只会触发一次 SQL 语句,而且非常简洁。在过去我们只能通过类似下面的语句来完成该查询: User.find(:all, :conditions => [“active = true AND created_at > ? AND gender = ‘female’ AND age > 25”, 1.week.ago]。
4. 符合 DRY 精神,精心定义的 scope,可以避免很多重复的 SQL conditions,其复用性相当高!
5. scope 返回的对象仍然具备所有 SQL 查询功能。例如,User.active.rencet.find_by_first_name(“Jim”),或User.active.recent.find_all_by_last_name(“Green”),同样可以工作,而且仍然只触发一次 SQL 查询。
Named scope 需要注意的问题:
1. 嵌套 scope 只能提供交集查询,如果要查询多个 scope 的并集,只能通过数组的 + 操作。例如,User.active + User.recent。但是这样做的一个问题是返回的结果为普通的数组,我们无法再使用上述第5个好处了。针对该问题,我们的处理方法是在必要时候,定义更灵活的 scope。例如,使用 scope 的参数传递功能:
named_scope :active_or_recent, lambda { |args*|
time_ago = args.first
if time_ago
{ :conditions => [‘active = true OR created_at > ?’, time_ago] }
else
{ :conditions => [‘active = true’] }
end
}
这样,User.active_or_recent(7.days.ago) 使用的条件为{ :conditions => [‘active = true OR created_at > ?’, time_ago] },而 User.active_or_recent 使用的条件为{ :conditions => [‘active = true’] }。
2. 突然忘了,请等待更新:)
