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