Carga ansiosa polimórfica


Usando Rails 3.2, ¿qué tiene de malo este código?

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

Plantea este error:

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

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

¿Cómo puedo filtrar basado en reviewable_type y reviewable.shop_type (que en realidad es shop.shop_type)?

Author: Ilya Lavrov, 2013-04-20

4 answers

Mi conjetura es 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 compilar 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 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 de la siguiente manera:

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

Observe que el nombre de la tabla es shops y no reviewable. No debe haber una tabla llamada revisable en la base de datos.

Creo que esto es más fácil y más flexible que definir explícitamente el join entre Review y Shop ya que le permite cargar ansiosamente además de consultar por campos relacionados.

La razón por la que esto es necesario es que ActiveRecord no puede construir una unión basada solo en revisable, ya que varias tablas representan el otro extremo de la unión, y SQL, por lo que sé, no le permite unirse a una tabla nombrado por el valor almacenado en una columna. Al definir la relación extra belongs_to :shop, está dando a ActiveRecord la información que necesita para completar la unión.

 172
Author: Sean Hill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-15 15:20:05

Si obtiene un error ActiveRecord::EagerLoadPolymorphicError, es porque includes decidió llamar a eager_load cuando las asociaciones polimórficas solo son soportadas por preload. Está en la documentación aquí: http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.html

Así que siempre use preload para asociaciones polimórficas. Hay una advertencia para esto: no se puede consultar la asociación polimórfica en las cláusulas where (lo cual tiene sentido, ya que la asociación polimórfica representa varias tablas.)

 4
Author: seanmorton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-19 20:43:56

Como anexo 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 está obteniendo errores de tabla indefinidos.

Así:

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

Sin la opción :include, si simplemente accede a la asociación review.shop en el ejemplo anterior, obtendrá un error indefinido ( probado en Rails 3, no 4 ) porque la asociación hará SELECT FROM shops WHERE shop.id = 1 AND ( reviews.review_type = 'Shop' ).

La opción :include forzará una UNIÓN en su lugar. :)

 1
Author: Stewart Mckinney,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-19 16:16:35
@reviews = @user.reviews.includes(:user, :reviewable)
.where('reviewable_type = ? AND reviewable.shop_type = ?', 'Shop', 'cafe').references(:reviewable)

Cuando está utilizando fragmentos SQL con WHERE, las referencias son necesarias para unirse a su asociación.

 0
Author: un_gars_la_cour,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-02 13:20:41