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経由でテストする簡単な方法はありますか?