Carga ansiosa polimórfica

104

Usando Rails 3.2, ¿qué pasa con este código?

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

Provoca este error:

No se puede cargar ansiosamente la asociación polimórfica: revisable

Si elimino la reviewable.shop_type = ?condición, funciona.

¿Cómo puedo filtrar en función de reviewable_typey reviewable.shop_type(que en realidad es shop.shop_type)?

Víctor
fuente

Respuestas:

207

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.

  1. ActiveRecord no puede generar la combinación sin información adicional.
  2. No hay una tabla llamada revisable

Para resolver este problema, debe definir explícitamente la relación entre Reviewy 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 shopsy 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 joinintermedio Reviewy, Shopya 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.

Sean Hill
fuente
6
En realidad terminé usando esto sin declarar nada más:@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
Eso es porque :reviewablees Shop. Las fotos pertenecen a la tienda.
Victor
6
trabajó en rails4, pero dará una advertencia de desaprobación, dijo que debería usar un estilo como has_many: spam_comments, -> {where spam: true}, class_name: 'Comment'. Por lo tanto, en rails4, será pertenece_ a: tienda, -> {donde ("reviews.reviewable_type = 'Tienda'")}, Foreign_key: 'reviewable_id'.Pero cuidado, Review.includes (: shop) generará un error, debe anexar al menos una cláusula where.
raykin
49
También hay Foreign_type, que me funcionó para un problema similar:belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id'
A5308Y
14
Cuando se carga, reviewsincluida la carga ansiosa del shopcódigo de uso asociado , Review.includes(:shop) la definición pertenece_to belongs_to :shop, -> { where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id' arroja un mensaje de error missing 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'
Jignesh Gohel
12

Si obtiene un ActiveRecord :: EagerLoadPolymorphicError, es porque includesdecidió llamar eager_loadcuando las asociaciones polimórficas solo son compatibles con preload. Está en la documentación aquí: http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.html

Por lo tanto, utilice siempre preloadpara 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).

seanmorton
fuente
Veo que ese es el único método que no está documentado en las guías: guides.rubyonrails.org/…
MSC
0
@reviews = @user.reviews.includes(:user, :reviewable)
.where('reviewable_type = ? AND reviewable.shop_type = ?', 'Shop', 'cafe').references(:reviewable)

Cuando utiliza fragmentos de SQL con WHERE, es necesario hacer referencias para unirse a su asociación.

un_gars_la_cour
fuente
0

Como apéndice, la respuesta en la parte superior, que es excelente, también puede especificar :includeen 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:

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

Sin la :includeopción, si simplemente accede a la asociación review.shopen 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 :includeopción forzará un JOIN en su lugar. :)

Stewart Mckinney
fuente
5
Clave desconocida: condiciones. Las claves válidas son:: class_name,: class,: Foreign_key,: validate,: autoguardado,: dependiente,: primary_key,: inverse_of,: required,: Foreign_type,: polymorphic,: touch,: counter_cache
Bengala