渴望负载多态


104

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

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

它引发此错误:

无法急切加载多态关联:可审查

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

如何根据reviewable_type和过滤reviewable.shop_type(实际上是shop.shop_type)?

Answers:


207

我的猜测是您的模型如下所示:

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. 没有所谓的可审查表

为了解决这个问题,你需要明确定义之间的关系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的表。

我相信,这比显式定义join之间的方法更容易,更灵活ReviewShop因为它不仅使您可以按相关字段进行查询,而且还渴望加载。

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


6
实际上,我最终在没有声明的情况下使用了它:@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
这是因为:reviewableShop。照片属于网店。
维克多

6
在rails4上工作,但是会给出弃用警告,它说应该使用has_many:spam_comments,-> {where spam:true},class_name:'Comment'这样的样式。因此在rails4中,将是归属地:shop,-> {where(“ reviews.reviewable_type ='Shop'”)},外键:'reviewable_id'。但是请注意,Review.includes(:shop)将引发错误,它必须至少附加一个where子句。
raykin 2014年

49
也有foreign_type,它为我解决了类似的问题:belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id'
A5308Y 2015年

14
当加载reviews包括急于加载相关的shop使用代码时Review.includes(:shop) ,belongs_to定义 belongs_to :shop, -> { where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id' 抛出错误提示missing FROM-clause entry for table "reviews"。我通过以下方式更新belongs_to定义来修复它: belongs_to :shop, -> { joins(:reviews) .where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
Jignesh Gohel

12

如果收到ActiveRecord :: EagerLoadPolymorphicError,这是因为当仅支持多态关联时includes决定调用。在此处的文档中:http : //api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.htmleager_loadpreload

因此,始终preload用于多态关联。需要注意的是:您不能在where子句中查询多态关联(这很有意义,因为多态关联表示多个表。)


我看到这是指南中未记录的一种方法:guides.rubyonrails.org/…–
MSC

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

当您将SQL片段与WHERE一起使用时,必须有引用才能加入关联。


0

作为附录的顶部答案,这是个很好的答案,您也可以: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选项将强制执行JOIN。:)


5
未知键::conditions。有效的密钥是::class_name,:class,:foreign_key,:validate,:autosave,:depend,:primary_key,:inverse_of,:required,:foreign_type,:polymorphic,:touch,:counter_cache
Bengala
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.