2017-11-02 2 views
1

Rails 5.1アプリには2つのモデルがあります。マスターはプライマリモデルであり、Itemはそれに属します。特別な使用例については、私はmaster.items.buildの#buildメソッドをオーバーライドする必要があります。この新しいビルドメソッドでは、他の項目の特定の条件を調べるためのクエリを作成する必要があります。Rails関連ブロックはクエリスコープに影響します。どうして?

これを実行すると、関連ブロックがクエリの範囲を変更するように見えるような予期しない(私自身の)動作が発生します。

master.rb

class Master < ApplicationRecord 

    has_many :items, inverse_of: :master do 
    def build att=nil 
     item = self.new(master: proxy_association.owner)  
     item.description1 = self.where(master_id: nil).to_sql 
     item.description2 = self.unscoped.where(master_id: nil).to_sql 
     item 
    end 
    end 

end 

master_test.rb要するに

require 'test_helper' 

class MasterTest < ActiveSupport::TestCase 

    test "master build outside association works as expected" do 

    m = Master.create! name: 'Bob' 
    item = Item.create master: m, name: 'Wall' 

    puts "Master: #{m}" 
    puts "self.where(master_id: nil).to_sql" 
    puts item.description1 
    puts "self.unscoped.where(master_id: nil).to_sql" 
    puts item.description2 

    # Flawed understanding would expect the queries to be the same 
    assert item.description1 == item.description2 
    end 

    test "master association blocks add an unexpected scope" do 

    m = Master.create! name: 'Bob' 
    item = m.items.build name: 'Wall' 

    puts "Master: #{m}" 
    puts "self.where(master_id: nil).to_sql" 
    puts item.description1 
    puts "self.unscoped.where(master_id: nil).to_sql" 
    puts item.description2 

    # Flawed understanding would expect the queries to be the same 
    assert item.description1 == item.description2 
    end 
end 

、再

class Item < ApplicationRecord 
    belongs_to :master 

    before_create -> { 
    self.description1 = self.class.where(master_id: nil).to_sql 
    self.description2 = self.class.unscoped.where(master_id: nil).to_sql 
    } 

end 

item.rb最初のテストのsultは期待どおりに動作します。私は、これはどこかに文書化し、誰かが私を指すことができることを期待しています見ていない

Master: #<Master:0x0055c3ee11b770> 
self.where(master_id: nil).to_sql 
SELECT "items".* FROM "items" WHERE "items"."master_id" = 980190963 AND "items"."master_id" IS NULL 
self.unscoped.where(master_id: nil).to_sql 
SELECT "items".* FROM "items" WHERE "items"."master_id" IS NULL 

:どちらも、スコープ外と標準クエリはに関連ブロック結果内の同じクエリを呼び出して、第二の試験のexpected.The結果として機能しますアソシエーションブロック内のこの自動スコープの理由、およびそれが説明されている場所。私はそれを疑いなく受け入れるだろうが、この振る舞いは(テストが失敗し始めたアプリケーションに基づいて)ある時点で変更されたように見える。私は古いバージョン4.2.9のアプリでもこれを体験しました。ここでは、過去にこのスコープなしで動作することが分かっています。再現するために必要であれば、ここで

は、テストアプリでGitHubのレポです:https://github.com/philayres/test_assoc_blocks

は、それを証明するためにrails db:migraterake testのように単純であるべき。

私はアイデアと説明について開いています。

答えて

0

たぶん私はこれを正しく理解していないよ。この場合

m = Master.create! name: 'Bob' 
item = m.items.build name: 'Wall' 

puts "Master: #{m}" 
puts "self.where(master_id: nil).to_sql" 
puts item.description1 
puts "self.unscoped.where(master_id: nil).to_sql" 
puts item.description2 

# Flawed understanding would expect the queries to be the same 
assert item.description1 == item.description2 

、ビルド方法にスコープを渡している。

item = m.items.build name: 'Wall' 

この範囲はItem.where(MASTER_IDです: m.id)。上書きされたビルド方法インサイド

は、あなたがしてください: - あなたは

を持っている第二の方法について

Item.where(master_id: m.id).where(master_id: nil).to_sql 

になります

item.description1 = self.where(master_id: nil).to_sql 

自己は、ここでは、クラスのスコープのインスタンスになります

self.unscoped.where(master_id: nil).to_sql 

最初のスコープが削除され、where句が適用されます。なぜあなたが電話をしたいのか分かりません

m.items 

特にmに関連付けられている項目は望ましくない場合。

はこれを確認するには、

self.to_sql 

と自己が実際にブロック内であるかを調べてみてください。

+0

ありがとうございます。問題は、私が明示的に定義していない範囲が適用されているのを見ていることです。魔法の範囲は、私が構築している親の「マスター」オブジェクトに基づいているように見えます。問合せは暗黙的に次のようにスコープされます。 'SELECT "items"。* FROM "items" WHERE "items"。 "master_id" = 980190963 [...] '。これまでのあなたの答えに基づいて、私はこのスコープが突然現れる理由をまだ見ていません。私はなぜこのスコープが他の場所にも見えないときにアソシエーションブロックの内側に適用される理由についての説明を探しています。 – Phil

+0

申し訳ありませんが、あなたはm.itemsを呼び出すときにそれを見ていませんか?それはこのスコープを適用するものです。 – oowowaee

+0

はい、私はそれがスコープを適用している理由を理解していません。ブロックの外にm.itemsを使用すると、スコープはありません。アソシエーションブロックでスコープが適用される理由を説明する説明がありますか? – Phil

関連する問題