Ruby on Rails 命名作用域实现

3

来自《使用Rails进行敏捷Web开发》一书

class Order < ActiveRecord::Base
   named_scope :last_n_days, lambda { |days| {:conditions =>
      ['updated < ?' , days] } }

   named_scope :checks, :conditions => {:pay_type => :check}
end

这个声明

orders = Orders.checks.last_n_days(7)

这将导致只有一个查询到数据库的结果。

Rails是如何实现这个功能的呢?我对Ruby还不熟悉,想知道是否有特殊的结构可以实现这个功能。

为了能够像这样链式调用方法,named_scope生成的函数必须返回自身或者可以进一步进行作用域限定的对象。但是,Ruby如何知道它是最后一个函数调用,并且现在应该查询数据库呢?

我之所以问这个问题,是因为上面的语句实际上会查询数据库,而不仅仅是返回由链式调用产生的SQL语句。

3个回答

7
在named_scope魔法中使用了两个技巧(或模式)。 代理模式 - 在类或关联上调用命名范围方法始终返回ActiveRecord :: NamedScope :: Scope类的实例,而不是过滤后的AR对象集合。尽管这种模式非常有用,但有时会使事情变得有些模糊,因为代理对象在其性质上是模棱两可的。 惰性加载 - 由于惰性加载(在此上下文中意味着仅在必要时访问数据库),可以将命名范围链接到需要使用范围定义的集合的点。每当您请求底层集合时,所有链接的范围都会被评估并执行数据库查询。
最后注意:在IRB中玩命名范围(或使用某种委托的任何东西)时,有一件事情需要记住。每次按Enter键时,都会评估您之前编写的内容,并调用返回值上的inspect方法。在链接命名范围的情况下,尽管整个表达式都被评估为Scope实例,但当IRB在其上调用inspect方法时,会对范围进行评估并触发数据库查询。这是由于通过委托传播了inspect方法,直到底层集合。

3

您可能想要尝试这个

class Order < ActiveRecord::Base

  class << self
    def last_n_days(n)
      scoped(:conditions => ['updated < ?', days])
    end
    def checks
      scoped(:conditions => {:pay_type => :check})
    end
  end

end

使用方法相同

@orders = Order.last_n_days(5)
@orders = Order.checks
@orders = Order.checks.last_n_days(5)

这仍然可以进行您所喜爱的惰性加载。也就是说,在您尝试访问记录之前,它不会进行查询。额外收获:与Rails 3兼容!

命名作用域已死


0
非常酷。我曾经想用JavaScript做类似的事情,但是JavaScript的行为相当奇怪。
这个声明:
var x = SomeObject;

不会调用SomeObject的toString()函数。但是这个语句:

var x;
x = SomeObject;

按预期正确调用了toString()函数。

这会阻止Javascript使用链式调用进行酷炫的操作。=(


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接