Was ist mit diesem Code falsch?
@reviews = @user.reviews.includes(:user, :reviewable)
.where('reviewable_type = ? AND reviewable.shop_type = ?', 'Shop', 'cafe')
Es tritt dieser Fehler auf:
Kann die polymorphe Assoziation nicht eifrig laden: überprüfbar
Wenn ich die reviewable.shop_type = ?
-Bedingung entferne, funktioniert es.
Wie kann ich basierend auf reviewable_type
und reviewable.shop_type
filtern (was eigentlich shop.shop_type
ist)?
Meine Vermutung ist, dass Ihre Modelle so aussehen:
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
Sie können diese Abfrage aus verschiedenen Gründen nicht durchführen.
Um dieses Problem zu lösen, müssen Sie die Beziehung zwischen Review
und Shop
explizit definieren.
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
Dann kannst du so abfragen:
Review.includes(:shop).where(shops: {shop_type: 'cafe'})
Beachten Sie, dass der Tabellenname shops
und nicht reviewable
ist. In der Datenbank sollte es keine Tabelle geben, die als überprüfbar bezeichnet wird.
Ich glaube, dass dies einfacher und flexibler ist, als die join
zwischen Review
und Shop
explizit zu definieren, da Sie damit neben dem Abfragen nach verwandten Feldern auch ein eifrigeres Laden ermöglichen.
Der Grund dafür ist, dass ActiveRecord keine Verknüpfung erstellen kann, die nur auf überprüfbarer Ebene basiert, da mehrere Tabellen das andere Ende der Verknüpfung darstellen, und SQL, soweit ich weiß, es nicht zulässt, dass Sie eine Tabelle mit dem gespeicherten Wert verknüpfen in einer Spalte. Durch das Definieren der zusätzlichen Beziehung belongs_to :shop
geben Sie ActiveRecord die Informationen, die zum Abschluss des Joins erforderlich sind.
Wenn Sie ein ActiveRecord :: EagerLoadPolymorphicError erhalten, liegt dies daran, dass includes
sich für eager_load
entschieden hat, wenn polymorphe Assoziationen nur von preload
unterstützt werden. Es ist in der Dokumentation hier: http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.html
Verwenden Sie also immer preload
für polymorphe Assoziationen. Es gibt jedoch eine Einschränkung: Sie können die polymorphe Zuordnung in where-Klauseln nicht abfragen (was sinnvoll ist, da die polymorphe Zuordnung mehrere Tabellen darstellt.)
Als Nachtrag der Antwort oben, die ausgezeichnet ist, können Sie auch :include
für die Verknüpfung angeben, wenn aus irgendeinem Grund die von Ihnen verwendete Abfrage die Tabelle des Modells nicht enthält und undefinierte Tabellenfehler auftreten.
So wie:
belongs_to :shop,
foreign_key: 'reviewable_id',
conditions: "reviews.reviewable_type = 'Shop'",
include: :reviews
Wenn Sie ohne die :include
-Option lediglich auf die Assoziation review.shop
im obigen Beispiel zugreifen, erhalten Sie einen UndefinedTable-Fehler (getestet in Rails 3, nicht in 4), da die Assoziation SELECT FROM shops WHERE shop.id = 1 AND ( reviews.review_type = 'Shop' )
ausführen wird.
Die :include
-Option erzwingt stattdessen einen JOIN. :)
@reviews = @user.reviews.includes(:user, :reviewable)
.where('reviewable_type = ? AND reviewable.shop_type = ?', 'Shop', 'cafe').references(:reviewable)
Wenn Sie SQL-Fragmente mit WHERE verwenden, sind Verweise erforderlich, um Ihrer Verknüpfung beizutreten.