webentwicklung-frage-antwort-db.com.de

Eifrige Last polymorph

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)?

85
Victor

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. 

  1. ActiveRecord kann den Join nicht ohne zusätzliche Informationen erstellen. 
  2. Es gibt keine Tabelle, die als überprüfbar bezeichnet wird

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.

181
Sean Hill

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.)

4
seanmorton

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. :)

1
@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.

0
un_gars_la_cour