web-dev-qa-db-fra.com

Eager charge polymorphe

En utilisant Rails 3.2, qu'est-ce qui ne va pas avec ce code?

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

Il soulève cette erreur:

Impossible de charger avec impatience l'association polymorphe: révisable

Si je supprime la condition reviewable.shop_type = ?, cela fonctionne.

Comment puis-je filtrer en fonction de reviewable_type et reviewable.shop_type (qui est en fait shop.shop_type)?

85
Victor

Je suppose que vos modèles ressemblent à ceci:

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

Vous ne pouvez pas faire cette requête pour plusieurs raisons. 

  1. ActiveRecord ne peut pas créer la jointure sans informations supplémentaires. 
  2. Il n'y a pas de table appelée reviewable

Pour résoudre ce problème, vous devez définir explicitement la relation entre Review et 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

Ensuite, vous pouvez interroger comme ceci:

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

Notez que le nom de la table est shops et non reviewable. Il ne devrait pas y avoir de table appelée révisable dans la base de données.

Je pense que cela est plus facile et plus flexible que de définir explicitement la join entre Review et Shop puisqu'elle vous permet de charger rapidement en plus de la recherche par champs connexes.

Cela est nécessaire parce qu'ActiveRecord ne peut pas créer de jointure basée uniquement sur révisable, car plusieurs tables représentent l'autre extrémité de la jointure et que SQL, à ma connaissance, ne vous permet pas de joindre une table nommée par la valeur stockée dans une colonne. En définissant la relation supplémentaire belongs_to :shop, vous donnez à ActiveRecord les informations nécessaires pour mener à bien la jointure.

181
Sean Hill

Si vous obtenez un ActiveRecord :: EagerLoadPolymorphicError, c'est parce que includes a décidé d'appeler eager_load lorsque les associations polymorphes sont uniquement prises en charge par preload. C'est dans la documentation ici: http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.html

Donc, utilisez toujours preload pour les associations polymorphes. Il y a un inconvénient à cela: vous ne pouvez pas interroger l'association polymorphe dans les clauses where (ce qui est logique, car l'association polymorphe représente plusieurs tables).

4
seanmorton

En tant qu'addenda, la réponse en haut, qui est excellente, vous pouvez également spécifier :include sur l'association si, pour une raison quelconque, la requête que vous utilisez n'inclut pas la table du modèle et que vous obtenez des erreurs non définies.

Ainsi:

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

Sans l'option :include, si vous accédez simplement à l'association review.shop dans l'exemple ci-dessus, vous obtiendrez une erreur UndefinedTable (testée dans Rails 3 et non 4), car l'association effectuera SELECT FROM shops WHERE shop.id = 1 AND ( reviews.review_type = 'Shop' )

L'option :include force un JOIN à la place. :)

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

Lorsque vous utilisez des fragments SQL avec WHERE, des références sont nécessaires pour rejoindre votre association.

0
un_gars_la_cour