在Rails 4中测试命名范围的最佳方法

时间:2013-09-20 08:39:47

标签: lambda ruby-on-rails-4 rspec2 named-scope proc

作为从Rails 3.2迁移到Rails 4的一部分,所有命名范围都需要一个proc块。在此处阅读更多内容:http://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html#active-record

我错过了在我的一个模型中更新范围,最终在我迁移后将我从生产中咬了一下。所以我想弄清楚如何测试这个问题,我发现了一些奇怪的行为。

在某些情况下,范围似乎在没有proc的情况下正常工作,但在其他情况下则不然。

# models/offer.rb
class Offer < ActiveRecord::Base

  scope :roster, where(:on_roster => true)
  scope :commit, where("status_id > 5")

end

如果我在rails控制台中对独立调用使用每个范围选项,则会正确构建查询,并且结果会像Rails 3.2中预期的那样返回:

$ rails c
2.0.0-p247 :001 > Offer.roster.all.size
  Offer Load (1.6ms)  SELECT "offers".* FROM "offers" WHERE "offers"."on_roster" = 't'
=> 1
2.0.0-p247 :002 > Offer.commit.all.size
  Offer Load (1.6ms)  SELECT "offers".* FROM "offers" WHERE (status_id > 5)
=> 3

但是,如果我在rails控制台中将两个范围调用链接在一起,则每个查询中只包含链中最后一个范围的约束:

2.0.0-p247 :003 > Offer.roster.commit.all.size
  Offer Load (1.4ms)  SELECT "offers".* FROM "offers" WHERE (status_id > 5)
 => 3
2.0.0-p247 :004 > Offer.commit.roster.all.size
  Offer Load (0.7ms)  SELECT "offers".* FROM "offers" WHERE "offers"."on_roster" = 't'
 => 1 

现在,如果我编辑我的模型,将proc添加到第二个命名范围,如下所示:

class Offer < ActiveRecord::Base

  scope :roster, where(:on_roster => true)
  scope :commit, -> { where("status_id > 5") }

end

如果带有proc定义的命名范围位于链的末尾,它将使用两组约束构建查询。但是,如果没有定义proc的命名范围位于链的末尾,则生成的查询将在没有定义proc的范围约束的情况下构建。

$ rails c
2.0.0-p247 :003 > Offer.roster.commit.all.size
  Offer Load (1.4ms)  SELECT "offers".* FROM "offers" WHERE "offers"."on_roster" = 't' AND (status_id > 5)
 => 0 
2.0.0-p247 :004 > Offer.commit.roster.all.size
  Offer Load (0.7ms)  SELECT "offers".* FROM "offers" WHERE "offers"."on_roster" = 't'
 => 1 

所以第一个结果是正确的,并加载两个范围,但第二个是不正确的,只加载最后一个范围。然后,如果你将两个范围都改为使用procs,就像这样:

# models/offer.rb
class Offer < ActiveRecord::Base

  scope :roster, -> { where(:on_roster => true) }
  scope :commit, -> { where("status_id > 5") }

end

你终于得到了预期的行为:

$ rails c
2.0.0-p247 :002 > Offer.roster.commit.all.size
  Offer Load (1.3ms)  SELECT "offers".* FROM "offers" WHERE "offers"."on_roster" = 't' AND (status_id > 5)
 => 0
2.0.0-p247 :001 > Offer.commit.roster.all.size
  Offer Load (1.7ms)  SELECT "offers".* FROM "offers" WHERE "offers"."on_roster" = 't' AND (status_id > 5)
 => 0 

关于此问题的一个注意事项,在您更新并保存模型后,rails控制台中的调用reload!将不会更新范围的行为。您必须结束rails控制台会话并开始新的会话以使proc与非proc正确接收。

我的问题是如何测试以确保我的所有范围都按预期运行?每次我想测试它们是否有proc或lambda块时,将范围链接在一起似乎非常混乱。然而,我在示波器上设置的简单测试告诉我,我的所有示波器都在通过,并给出了误报结果。

是否有一种简单的方法可以通过Rails4使用Rails4测试命名范围是否位于proc或lambda块中?

1 个答案:

答案 0 :(得分:0)

范围只是定义类方法的语法糖,因此通过查看代码,很难知道范围是否为proc / lambda。

我能想到的唯一解决方案是使用RR并代理范围方法。这样,如果正文不响应呼叫,您可以引发异常。在您的测试中,您预期不会引发任何异常。但我怀疑这是否会起作用,因为一旦你设置了代理,那么类已经被加载了,因此调用了scope方法。

我猜测而不是通过测试强制执行Proc使用,而是覆盖scope方法以完全不允许非procs / -lambdas。

供参考: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/scoping/named.rb