Supongo que sus modelos se ven así:
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
No puede realizar esa consulta por varias razones.
- ActiveRecord no puede generar la combinación sin información adicional.
- No hay una tabla llamada revisable
Para resolver este problema, debe definir explícitamente la relación entre Review
y 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
Entonces puedes consultar así:
Review.includes(:shop).where(shops: {shop_type: 'cafe'})
Observe que el nombre de la tabla es shops
y no reviewable
. No debería haber una tabla denominada revisable en la base de datos.
Creo que esto es más fácil y más flexible que definir explícitamente el join
intermedio Review
y, Shop
ya que le permite realizar una carga ansiosa además de realizar consultas por campos relacionados.
La razón por la que esto es necesario es que ActiveRecord no puede construir una combinación basada en solo revisable, ya que varias tablas representan el otro extremo de la combinación, y SQL, hasta donde yo sé, no le permite unirse a una tabla nombrada por el valor almacenado en una columna. Al definir la relación adicional belongs_to :shop
, le está dando a ActiveRecord la información que necesita para completar la unión.
@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)
:reviewable
esShop
. Las fotos pertenecen a la tienda.belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id'
reviews
incluida la carga ansiosa delshop
código de uso asociado ,Review.includes(:shop)
la definición pertenece_tobelongs_to :shop, -> { where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
arroja un mensaje de errormissing FROM-clause entry for table "reviews"
. Lo arreglé actualizando la definición de pertenece_to de la siguiente manera:belongs_to :shop, -> { joins(:reviews) .where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
Si obtiene un ActiveRecord :: EagerLoadPolymorphicError, es porque
includes
decidió llamareager_load
cuando las asociaciones polimórficas solo son compatibles conpreload
. Está en la documentación aquí: http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.htmlPor lo tanto, utilice siempre
preload
para asociaciones polimórficas. Hay una advertencia para esto: no puede consultar la asociación polimórfica en cláusulas where (lo cual tiene sentido, ya que la asociación polimórfica representa varias tablas).fuente
Cuando utiliza fragmentos de SQL con WHERE, es necesario hacer referencias para unirse a su asociación.
fuente
Como apéndice, la respuesta en la parte superior, que es excelente, también puede especificar
:include
en la asociación si, por alguna razón, la consulta que está utilizando no incluye la tabla del modelo y obtiene errores de tabla no definidos.Al igual que:
Sin la
:include
opción, si simplemente accede a la asociaciónreview.shop
en el ejemplo anterior, obtendrá un error UndefinedTable (probado en Rails 3, no 4) porque la asociación funcionaráSELECT FROM shops WHERE shop.id = 1 AND ( reviews.review_type = 'Shop' )
.La
:include
opción forzará un JOIN en su lugar. :)fuente