0

作为从 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 块时,将作用域链接在一起似乎非常混乱。然而,我在示波器上设置的简单测试告诉我,我所有的示波器都通过了,并给出了假阳性结果。

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

4

1 回答 1

0

Scopes are only syntactic sugar for defining class methods, so by looking at the code it's quite impossible to know whether your scope was a proc/lambda or not.

Only solution I could think of is using RR and proxying the scope method. This way you could raise an exception if body does not respond to call. In your tests you than expect that no exception is raised. But I doubt this is going to work because once you set up the proxy the class has already been loaded and, thus, the scope method has been called.

I guess instead of enforcing Proc usage through test, overwriting the scope method to not allow non-procs/-lambdas at all is a better idea.

For reference: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/scoping/named.rb

于 2013-09-20T14:58:35.267 回答