Carico desideroso polimorfico


104

Usando Rails 3.2, cosa c'è di sbagliato in questo codice?

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

Solleva questo errore:

Impossibile caricare con entusiasmo l'associazione polimorfica: revisionabile

Se rimuovo la reviewable.shop_type = ?condizione, funziona.

Come posso filtrare in base a reviewable_typee reviewable.shop_type(che è effettivamente shop.shop_type)?

Risposte:


207

La mia ipotesi è che i tuoi modelli abbiano questo aspetto:

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

Non sei in grado di eseguire quella query per diversi motivi.

  1. ActiveRecord non è in grado di creare il join senza ulteriori informazioni.
  2. Non esiste una tabella denominata revisionabile

Per risolvere questo problema, è necessario definire in modo esplicito la relazione tra Reviewe 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

Quindi puoi eseguire una query in questo modo:

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

Notare che il nome della tabella è shopse non reviewable. Non dovrebbe esserci una tabella chiamata revisionabile nel database.

Credo che questo sia più facile e più flessibile rispetto alla definizione esplicita del jointra Reviewe Shoppoiché consente di caricare con entusiasmo oltre a eseguire query per campi correlati.

Il motivo per cui ciò è necessario è che ActiveRecord non può creare un join basato sul solo revisionabile, poiché più tabelle rappresentano l'altra estremità del join e SQL, per quanto ne so, non ti consente di unirti a una tabella denominata dal valore memorizzato in una colonna. Definendo la relazione extra belongs_to :shop, si forniscono ad ActiveRecord le informazioni necessarie per completare il join.


6
In realtà ho finito per usarlo senza dichiarare altro:@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
Questo perché lo :reviewableè Shop. Le foto appartengono al negozio.
Victor

6
ha funzionato in rails4, ma darà un avviso di deprecazione, ha detto che dovrebbe usare uno stile come has_many: spam_comments, -> {where spam: true}, class_name: 'Comment'. Quindi, in rails4, sarà appartiene_to: negozio, -> {dove ("reviews.reviewable_type = 'Negozio'")}, chiave_estranea: 'reviewable_id'.Ma attenzione, Review.includes (: negozio) solleverà un errore, deve aggiungere in locazione una clausola where.
raykin

49
C'è anche foreign_type, che ha funzionato per me per un problema simile:belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id'
A5308Y

14
Durante il caricamento, reviewsincluso il caricamento desideroso del shopcodice utilizzando associato , Review.includes(:shop) la definizione appartiene_to belongs_to :shop, -> { where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id' genera un messaggio di errore missing FROM-clause entry for table "reviews". L'ho risolto aggiornando la definizione appartiene_to nel modo seguente: belongs_to :shop, -> { joins(:reviews) .where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
Jignesh Gohel

12

Se ottieni un ActiveRecord :: EagerLoadPolymorphicError, è perché ha includesdeciso di chiamare eager_loadquando le associazioni polimorfiche sono supportate solo da preload. È nella documentazione qui: http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.html

Quindi usa sempre preloadper associazioni polimorfiche. C'è un avvertimento per questo: non è possibile interrogare l'associazione polimorfica nelle clausole where (il che ha senso, poiché l'associazione polimorfica rappresenta più tabelle).


Vedo che questo è l'unico metodo non documentato nelle guide: guides.rubyonrails.org/…
MSC

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

Quando si utilizzano frammenti SQL con WHERE, i riferimenti sono necessari per unirsi alla propria associazione.


0

Come addendum la risposta in alto, che è ottima, puoi anche specificare :includesull'associazione se per qualche motivo la query che stai utilizzando non include la tabella del modello e stai ottenendo errori di tabella non definiti.

Così:

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

Senza l' :includeopzione, se si accede semplicemente all'associazione review.shopnell'esempio sopra, si otterrà un errore UndefinedTable (testato in Rails 3, non 4) perché l'associazione funzionerà SELECT FROM shops WHERE shop.id = 1 AND ( reviews.review_type = 'Shop' ).

L' :includeopzione forzerà invece un JOIN. :)


5
Chiave sconosciuta:: condizioni. Le chiavi valide sono:: class_name,: class,: foreign_key,: validate,: autosave,: dipendente,: primary_key,: inverse_of,: required,: foreign_type,: polymorphic,: touch,: counter_cache
Bengala
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.