Eifrige Last polymorph

104

Was ist mit Rails 3.2 an diesem Code falsch?

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

Dieser Fehler wird ausgelöst:

Kann die polymorphe Assoziation nicht eifrig laden: überprüfbar

Wenn ich die reviewable.shop_type = ?Bedingung entferne , funktioniert es.

Wie kann ich nach dem reviewable_typeund filtern reviewable.shop_type(was eigentlich ist shop.shop_type)?

Sieger
quelle

Antworten:

207

Ich vermute, 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 mehreren Gründen nicht ausführen.

  1. ActiveRecord kann den Join ohne zusätzliche Informationen nicht erstellen.
  2. Es gibt keine Tabelle mit der Bezeichnung "überprüfbar"

Um dieses Problem zu lösen, müssen Sie die Beziehung zwischen Reviewund explizit definieren 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

Dann können Sie wie folgt abfragen:

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

Beachten Sie, dass der Tabellenname lautet shopsund nicht reviewable. In der Datenbank sollte keine Tabelle mit dem Namen überprüfbar vorhanden sein.

Ich glaube, dies ist einfacher und flexibler als das explizite Definieren des joinDazwischen Reviewund Shopda es Ihnen ermöglicht, zusätzlich zum Abfragen nach verwandten Feldern eifrig zu laden.

Der Grund dafür ist, dass ActiveRecord keinen Join erstellen kann, der nur auf überprüfbar basiert, da mehrere Tabellen das andere Ende des Joins darstellen und SQL meines Wissens nicht zulässt, dass Sie einer Tabelle beitreten, die nach dem gespeicherten Wert benannt ist in einer Spalte. Indem Sie die zusätzliche Beziehung definieren belongs_to :shop, geben Sie ActiveRecord die Informationen, die zum Abschließen des Joins erforderlich sind.

Sean Hill
quelle
6
Eigentlich habe ich das benutzt, ohne etwas mehr zu deklarieren:@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
Das :reviewableliegt daran Shop. Fotos gehören zum Shop.
Victor
6
hat in Rails4 funktioniert, gibt aber eine Warnung vor Verfall aus. Es sollte ein Stil wie has_many: spam_comments, -> {where spam: true}, class_name: 'Comment' verwendet werden. Also wird in Rails4 gehören zu: shop, -> {where ("reviews.reviewable_type = 'Shop'")}, Foreign_key: 'reviewable_id'. Aber Vorsicht, Review.includes (: shop) wird Fehler auslösen, es muss mindestens eine where-Klausel anhängen.
Raykin
48
Es gibt auch einen fremden Typ, der für ein ähnliches Problem bei mir funktioniert hat:belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id'
A5308Y
14
Beim Laden, reviewseinschließlich des eifrigen Ladens des zugehörigen shopCodes, wird in Review.includes(:shop) der Definition davon_zu die belongs_to :shop, -> { where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id' Fehlermeldung ausgegeben missing FROM-clause entry for table "reviews". Ich habe es behoben, indem ich Gehorsame_Definition auf folgende Weise aktualisiert habe: belongs_to :shop, -> { joins(:reviews) .where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
Jignesh Gohel
11

Wenn Sie einen ActiveRecord :: EagerLoadPolymorphicError erhalten, liegt dies daran, dass Sie includessich für einen Aufruf entscheiden, eager_loadwenn polymorphe Assoziationen nur von unterstützt werden preload. Es befindet sich in der Dokumentation hier: http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.html

Also immer preloadfür polymorphe Assoziationen verwenden. Hierfür gibt es eine Einschränkung: Sie können die polymorphe Zuordnung in where-Klauseln nicht abfragen (was sinnvoll ist, da die polymorphe Zuordnung mehrere Tabellen darstellt.)

Seanmorton
quelle
Ich sehe, dass dies die einzige Methode ist, die in den Handbüchern
MSC
0
@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 Referenzen erforderlich, um Ihrer Zuordnung beizutreten.

un_gars_la_cour
quelle
0

Als Ergänzung zur Antwort oben, die ausgezeichnet ist, können Sie auch :includein der Zuordnung angeben , ob die von Ihnen verwendete Abfrage aus irgendeinem Grund die Modelltabelle nicht enthält und Sie undefinierte Tabellenfehler erhalten.

Wie so:

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

:includeWenn Sie ohne die Option review.shopim obigen Beispiel lediglich auf die Zuordnung zugreifen , wird ein UndefinedTable-Fehler (getestet in Rails 3, nicht 4) angezeigt, da die Zuordnung dies tut SELECT FROM shops WHERE shop.id = 1 AND ( reviews.review_type = 'Shop' ).

Die :includeOption erzwingt stattdessen einen JOIN. :) :)

Stewart Mckinney
quelle
5
Unbekannter Schlüssel :: Bedingungen. Gültige Schlüssel sind :: Klassenname ,: Klasse ,: Fremdschlüssel ,: Validieren ,: Autosave ,: Abhängig ,: Primärschlüssel ,: Inverse_von ,: Erforderlich ,: Fremd_Typ ,: Polymorph ,: Berühren ,: Gegen_Cache
Bengala