渴望加载多态

Vic*_*tor 98 activerecord ruby-on-rails ruby-on-rails-3

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

@reviews = @user.reviews.includes(:user, :reviewable)
.where('reviewable_type = ? AND reviewable.shop_type = ?', 'Shop', 'cafe')
Run Code Online (Sandbox Code Playgroud)

它引发了这个错误:

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

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

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

Sea*_*ill 195

我的猜测是你的模型看起来像这样:

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
Run Code Online (Sandbox Code Playgroud)

由于多种原因,您无法执行该查询.

  1. ActiveRecord无法在没有其他信息的情况下构建连接.
  2. 没有名为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
Run Code Online (Sandbox Code Playgroud)

然后你可以像这样查询:

Review.includes(:shop).where(shops: {shop_type: 'cafe'})
Run Code Online (Sandbox Code Playgroud)

请注意,表名是shopsreviewable.数据库中不应该有一个名为reviewable的表.

我相信这比明确定义join之间更容易和更灵活Review,Shop因为除了通过相关字段查询之外,它还允许您急切加载.

这是必要的原因是ActiveRecord不能单独基于可审阅的构建连接,因为多个表代表连接的另一端,并且据我所知,SQL不允许您加入由存储的值命名的表在一列中.通过定义额外关系belongs_to :shop,您将为ActiveRecord提供完成连接所需的信息.

  • 还有foreign_type,这对我来说也有类似的问题:`belongs_to:shop,foreign_type:'Shop',foreign_key:'reviewable_id'` (44认同)
  • 当加载"评论"包括急切加载相关的"商店"时,使用代码`Review.includes(:shop)`属性定义`belongs_to:shop, - > {where(reviews:{reviewable_type:'Shop'})},foreign_key :'reviewable_id'`抛出错误说'缺少FROM子句条目表"评论"`.我通过以下方式更新belongs_to定义来修复它:`belongs_to:shop, - > {join(:reviews).where(reviews:{reviewable_type:'Shop'})},foreign_key:'reviewable_id'` (12认同)
  • 曾在rails4工作,但会给出弃用警告,它说应该使用像has_many这样的样式:spam_comments, - > {where spam:true},class_name:'Comment'.所以在rails4中,将是belongs_to:shop, - > {where("reviews.reviewable_type ='Shop'")},foreign_key:'reviewable_id'.但要小心,Review.includes(:shop)会引发错误,它必须追加租赁一个where子句. (6认同)
  • 实际上我最终使用了这个而没有声明更多:`@reviews = @ user.reviews.joins("INNER JOIN shop ON(reviewable_type ='Shop'AND shops.id = reviewable_id AND shops.shop_type ='"+ type +" ')").包括(:user,:reviewable =>:photos)` (5认同)
  • 完美答案,详细解释.谢谢. (3认同)
  • 我试过这个,但它浮出水面一个非常有趣的错误。如果有一个商店和一个用户具有相同的数据库 ID,那么在将 `reviewable_type` 设置为 `User` 的评论上调用 `review.shop` 可能会返回一个完全不相关的 Shop 而不是 `nil`。在某些情况下,这可能是非常严重的数据泄漏,因为用户可能有权访问“review”,但不能访问“review.shop”返回的 Shop。 (3认同)
  • @phylae,我们遇到了完全相同的错误,并发现最好的解决方法是定义一个 `shop` 函数,如果 reviewable_type != "Shop" 则返回 nil,否则只需调用 super。```ruby def shop 返回除非 reviewable_type == "Shop" super end ``` (3认同)
  • 如果您希望能够对多个模型使用多态关联,则需要在“belongs_to”定义中包含“optional: true”。 (3认同)

小智 8

没有足够的声誉来发表评论来扩展上述 Moses Lucas 的响应,我必须进行一些小调整才能使其在 Rails 7 中工作,因为我收到以下错误:

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
Run Code Online (Sandbox Code Playgroud)

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

我和 belongs_to :shop, class_name: 'Shop', foreign_key: 'reviewable_id'

这里唯一的区别是更改foreign_type:class_name:


小智 7

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

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