熱心な負荷ポリモーフィック


104

Rails 3.2を使用して、このコードの何が問題になっていますか?

@reviews = @user.reviews.includes(:user, :reviewable)
.where('reviewable_type = ? AND reviewable.shop_type = ?', 'Shop', 'cafe')

このエラーが発生します:

多態的な関連付けを熱心に読み込めません:reviewable

reviewable.shop_type = ?条件を削除すると動作します。

reviewable_typeand reviewable.shop_type(これは実際にはshop.shop_type)に基づいてフィルタリングするにはどうすればよいですか?

回答:


207

私の推測では、モデルは次のようになります。

class User < ActiveRecord::Base
  has_many :reviews
end

class Review < ActiveRecord::Base
  belongs_to :user
  belongs_to :reviewable, polymorphic: true
end

class Shop < ActiveRecord::Base
  has_many :reviews, as: :reviewable
end

いくつかの理由でそのクエリを実行できません。

  1. ActiveRecordは、追加情報がないと結合を構築できません。
  2. レビュー可能という表はありません

この問題を解決するには、との関係を明示的に定義する必要がReviewありShopます。

class Review < ActiveRecord::Base
   belongs_to :user
   belongs_to :reviewable, polymorphic: true
   # For Rails < 4
   belongs_to :shop, foreign_key: 'reviewable_id', conditions: "reviews.reviewable_type = 'Shop'"
   # For Rails >= 4
   belongs_to :shop, -> { where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
   # Ensure review.shop returns nil unless review.reviewable_type == "Shop"
   def shop
     return unless reviewable_type == "Shop"
     super
   end
end

次に、次のようにクエリできます。

Review.includes(:shop).where(shops: {shop_type: 'cafe'})

テーブル名はでshopsあり、ではないことに注意してくださいreviewable。データベースにreviewableと呼ばれるテーブルがあってはなりません。

これは、明示的にjoinbetween Reviewを定義するよりも簡単で柔軟性があるとShop思います。これにより、関連フィールドによるクエリに加えて、積極的なロードが可能になります。

これが必要な理由は、複数のテーブルが結合のもう一方の端を表し、SQLは、私が知る限り、格納された値によって名前が付けられたテーブルに結合することを許可しないため、ActiveRecordはレビュー可能のみに基づいて結合を構築できないためです列に。追加の関係を定義することによりbelongs_to :shop、結合を完了するために必要な情報をActiveRecordに提供します。


6
実際、私はこれ以上何も宣言せずにこれを使用することになりました:@reviews = @user.reviews.joins("INNER JOIN shops ON (reviewable_type = 'Shop' AND shops.id = reviewable_id AND shops.shop_type = '" + type + "')").includes(:user, :reviewable => :photos)
Victor

1
それ:reviewableShopです。写真はショップに属しています。
ビクター

6
rails4で動作しましたが、非推奨の警告が表示されます。has_many:spam_comments、-> {where spam:true}、class_name: 'Comment'のようなスタイルを使用する必要があると述べています。したがって、rails4では、belongs_to:shop、-> {where( "reviews.reviewable_type = 'Shop'")}、foreign_key: 'reviewable_id'となります。ただし、Review.includes(:shop)はエラーを発生させます少なくとも1つのwhere句を追加します。
レイキン、2014

49
同様の問題のために私のために働いたforeign_typeもあります:belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id'
A5308Y

14
reviews関連付けられたshopコードを使用しReview.includes(:shop) た積極的なロードを含むロードを行うと、belongs_to定義 belongs_to :shop, -> { where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id' がエラーをスローしますmissing FROM-clause entry for table "reviews"。私は、次のように定義BELONGS_TO更新することで、それを修正: belongs_to :shop, -> { joins(:reviews) .where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
Jignesh Gohel

12

ActiveRecord :: EagerLoadPolymorphicErrorを受け取った場合、それincludeseager_load、ポリモーフィックな関連付けがでのみサポートされているときに呼び出すことにしたためpreloadです。それはここのドキュメントにあります:http : //api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.html

したがって、常にpreload多態的な関連付けに使用します。これには注意点が1つあります。where句でポリモーフィックな関連付けをクエリすることはできません(ポリモーフィックな関連付けが複数のテーブルを表すため、これは理にかなっています)。


私はそれがガイドに記載されていないですひとつの方法です参照してください。guides.rubyonrails.org/...
MSC

0
@reviews = @user.reviews.includes(:user, :reviewable)
.where('reviewable_type = ? AND reviewable.shop_type = ?', 'Shop', 'cafe').references(:reviewable)

WHEREでSQLフラグメントを使用している場合、関連付けを結合するには参照が必要です。


0

補足として、一番上の答えはすばらしいですが:include、何らかの理由で使用しているクエリにモデルのテーブルが含まれておらず、未定義のテーブルエラーが発生している場合は、関連付けで指定することもできます。

そのようです:

belongs_to :shop, 
           foreign_key: 'reviewable_id', 
           conditions: "reviews.reviewable_type = 'Shop'",
           include: :reviews

なければ:include、あなたは単に関連付けをアクセスした場合のオプション、review.shop上の例では関連が行いますので、あなたは(Railsの3でテスト、いない4)UndefinedTableエラーが発生しますSELECT FROM shops WHERE shop.id = 1 AND ( reviews.review_type = 'Shop' )

:includeオプションでは、代わりにJOINを強制します。:)


5
不明なキー::条件。有効なキーは次のとおり:CLASS_NAME、:クラス:FOREIGN_KEY、:検証:自動保存、:依存:PRIMARY_KEY:inverse_of、:必須、:foreign_type、:多型、タッチ、:counter_cache
ベンガラ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.