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. 突然忘了,请等待更新:)

Posted by Shaokun Thu, 18 Sep 2008 20:46:00 GMT