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
)?
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.
- ActiveRecord no puede compilar 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 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.
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.)
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. :)
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.
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