0

Rails 3.2 から Rails 4 への移行の一環として、すべての名前付きスコープに proc ブロックが必要です。詳細はこちら: http://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html#active-record

モデルの 1 つでスコープを更新し忘れていたため、移行後に実稼働環境で問題が発生しました。そこで、この問題をテストする方法を理解したかったのですが、奇妙な動作を見つけました。

場合によっては、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 つのスコープ呼び出しをチェーンすると、チェーンの最後のスコープからの制約のみが各クエリに含まれます。

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 を 2 番目の名前付きスコープに追加すると、次のようになります。

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 

したがって、最初の結果は正しく、両方のスコープをロードしますが、2 番目の結果は正しくなく、最後のスコープのみをロードします。次に、両方のスコープを 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 

これに関する 1 つの注意点として、モデルを更新して保存した後、Rails コンソールで呼び出してもスコープの動作は更新されません。reload!proc と non-proc を正しく取得するには、Rails コンソール セッションを終了して新しいセッションを開始する必要があります。

問題は、すべてのスコープが期待どおりに動作することを確認するためのテスト方法です。proc または lambda ブロックがあるかどうかをテストするたびに、スコープをチェーンするのは非常に面倒です。ただし、スコープに設定した簡単なテストでは、すべてのスコープが合格し、偽陽性の結果が得られたことがわかりました。

名前付きスコープがprocまたはラムダブロック内にあるかどうかをRails4でRspec経由でテストする簡単な方法はありますか?

4

1 に答える 1

0

スコープは、クラス メソッドを定義するための構文糖衣に過ぎないため、コードを見ただけでは、スコープが proc/lambda であったかどうかを知ることはまったく不可能です。

私が考えることができる唯一の解決策は、RR を使用してスコープ メソッドをプロキシすることです。このようにして、本体が呼び出しに応答しない場合に例外を発生させることができます。テストでは、例外が発生しないことを期待しています。しかし、プロキシをセットアップすると、クラスはすでにロードされており、スコープ メソッドが呼び出されているため、これがうまくいくとは思えません。

テストを通じて Proc の使用を強制する代わりに、scope非 procs/-lambdas をまったく許可しないようにメソッドを上書きすることをお勧めします。

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

于 2013-09-20T14:58:35.267 に答える