急切负载多态性

使用 Rails 3.2,这段代码有什么问题吗?

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

It raises this error:

Can not eagerly load the polymorphic association :reviewable

如果我删除 reviewable.shop_type = ?条件,它工作。

如何基于 reviewable_typereviewable.shop_type(实际上是 shop.shop_type)进行过滤?

59274 次浏览

我猜你们的模特是这样的:

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

由于以下几个原因,您无法执行该查询。

  1. ActiveRecord 无法在没有附加信息的情况下构建联接。
  2. There is no table called reviewable

要解决这个问题,您需要显式地定义 ReviewShop之间的关系。

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

Then you can query like this:

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

注意,表名是 shops而不是 reviewable。数据库中不应该存在称为可检查的表。

我相信这比显式定义 ReviewShop之间的 join更容易和更灵活,因为它允许您除了通过相关字段查询之外,还可以进行即时加载。

The reason that this is necessary is that ActiveRecord cannot build a join based on reviewable alone, since multiple tables represent the other end of the join, and SQL, as far as I know, does not allow you join a table named by the value stored in a column. By defining the extra relationship belongs_to :shop, you are giving ActiveRecord the information it needs to complete the join.

作为一个附录,顶部的答案非常好,如果由于某种原因您正在使用的查询不包括模型的表,并且您正在得到未定义的表错误,您也可以在关联上指定 :include

像这样:

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

如果没有 :include选项,如果您仅仅访问上面示例中的关联 review.shop,您将得到一个未定义表错误(在 Rails 3中测试,而不是4) ,因为关联将执行 SELECT FROM shops WHERE shop.id = 1 AND ( reviews.review_type = 'Shop' )

:include选项将强制执行 JOIN。 :)

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

在 WHERE 中使用 SQL 片段时,需要引用来加入关联。

If you get an ActiveRecord::EagerLoadPolymorphicError, it's because includes decided to call eager_load when polymorphic associations are only supported by preload. It's in the documentation here: http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.html

因此,始终使用 preload进行多态关联。对此有一个警告: 不能在 where 子句中查询多态关联(这是有意义的,因为多态关联表示多个表)

这个帮了我大忙

  belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id'


没有足够的声誉来扩展上面 Moses Lucas 的响应,我不得不做一个小小的调整来让它在 Rails7中工作,因为我收到了以下错误:

ArgumentError: Unknown key: :foreign_type. Valid keys are: :class_name, :anonymous_class, :primary_key, :foreign_key, :dependent, :validate, :inverse_of, :strict_loading, :autosave, :required, :touch, :polymorphic, :counter_cache, :optional, :default

而不是 belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id'

我选了 belongs_to :shop, class_name: 'Shop', foreign_key: 'reviewable_id'

The only difference here is changing foreign_type: to class_name:!