急切加载多态

Posted

技术标签:

【中文标题】急切加载多态【英文标题】:Eager load polymorphic 【发布时间】:2013-04-13 23:03:37 【问题描述】:

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

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

它引发了这个错误:

不能急切地加载多态关联:reviewable

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

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

【问题讨论】:

【参考方案1】:

我猜你的模型是这样的:

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

由于多种原因,您无法进行该查询。

    ActiveRecord 无法在没有其他信息的情况下构建连接。 没有可审核的表格

要解决这个问题,需要明确定义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

然后你可以这样查询:

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

注意表名是shops 而不是reviewable。数据库中不应该有一个名为 reviewable 的表。

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

这样做是必要的原因是 ActiveRecord 无法单独基于可审查建立连接,因为多个表代表连接的另一端,据我所知,SQL 不允许您连接名为的表存储在列中的值。通过定义额外的关系belongs_to :shop,您将向 ActiveRecord 提供完成连接所需的信息。

【讨论】:

实际上我最终使用了它而没有声明更多内容:@reviews = @user.reviews.joins("INNER JOIN shops ON (reviewable_type = 'Shop' AND shops.id = reviewable_id AND shops.shop_type = '" + type + "')").includes(:user, :reviewable =&gt; :photos) 在 rails4 中工作,但会给出弃用警告,它说应该使用类似 has_many :spam_cmets, -> where spam: true , class_name: 'Comment' 的样式。所以在 rails4 中,将是 belongs_to :shop, -> where("reviews.reviewable_type = 'Shop'"), foreign_key: 'reviewable_id'。但是要小心,Review.includes(:shop) 会引发错误,它必须至少附加一个 where 子句。 还有foreign_type,它为我解决了类似的问题:belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id' 当加载 reviews 包括使用代码 Review.includes(:shop) 急切加载关联的 shop 时,belongs_to 定义 belongs_to :shop, -&gt; where(reviews: reviewable_type: 'Shop') , foreign_key: 'reviewable_id' 会抛出错误,说 missing FROM-clause entry for table "reviews"。我通过以下方式更新belongs_to定义来修复它:belongs_to :shop, -&gt; joins(:reviews) .where(reviews: reviewable_type: 'Shop') , foreign_key: 'reviewable_id' 我试过这个,但它出现了一个非常有趣的错误。如果存在具有相同数据库 ID 的 Shop 和 User,则在将 reviewable_type 设置为 User 的评论上调用 review.shop 可能会返回完全不相关的 Shop 而不是 nil。在某些情况下,这可能是非常严重的数据泄露,因为用户可能有权访问review,但不能访问review.shop 返回的 Shop。【参考方案2】:

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

像这样:

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

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

:include 选项将改为强制加入。 :)

【讨论】:

未知键::条件。有效键是::class_name, :class, :foreign_key, :validate, :autosave, :dependent, :primary_key, :inverse_of, :required, :foreign_type, :polymorphic, :touch, :counter_cache【参考方案3】:
@reviews = @user.reviews.includes(:user, :reviewable)
.where('reviewable_type = ? AND reviewable.shop_type = ?', 'Shop', 'cafe').references(:reviewable)

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

【讨论】:

【参考方案4】:

如果你得到一个 ActiveRecord::EagerLoadPolymorphicError,那是因为 includes 决定调用 eager_load,而只有 preload 支持多态关联。它在此处的文档中:http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.html

所以总是使用preload 进行多态关联。对此有一个警告:您不能在 where 子句中查询多态关联(这是有道理的,因为多态关联代表多个表。)

【讨论】:

我知道这是指南中未记录的一种方法:guides.rubyonrails.org/…【参考方案5】:

这对我有用

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

【讨论】:

以上是关于急切加载多态的主要内容,如果未能解决你的问题,请参考以下文章

属性装饰器急切加载和一般使用 Django 的急切加载

何时在休眠中使用延迟加载/急切加载?

EF急切加载和延迟加载的区别?

实体框架急切加载不返回数据,延迟加载有

“过滤”急切加载的数据时出现问题

急切加载关系