渴望加载多态

时间:2013-04-20 18:00:04

标签: ruby-on-rails ruby-on-rails-3 activerecord

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

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

它引发了这个错误:

  

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

如果我删除了reviewable.shop_type = ?条件,则无效。

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

4 个答案:

答案 0 :(得分:186)

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

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. 没有名为reviewable
  3. 的表格

    要解决此问题,您需要明确定义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的表。

    我认为这比明确定义joinReview之间的Shop更容易,也更灵活,因为除了通过相关字段查询外,它还允许您急切加载。

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

答案 1 :(得分:4)

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

所以总是使用preload进行多态关联。有一点需要注意:你不能在where子句中查询多态关联(这是有意义的,因为多态关联代表多个表。)

答案 2 :(得分:0)

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

像这样:

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

如果没有:include选项,如果您只是访问上面示例中的关联review.shop,您将收到UndefinedTable错误(在Rails 3中测试,而不是4),因为关联将执行{{ 1}}。

SELECT FROM shops WHERE shop.id = 1 AND ( reviews.review_type = 'Shop' )选项会强制使用JOIN。 :)

答案 3 :(得分:0)

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

当您将SQL片段与WHERE一起使用时,需要引用以加入您的关联。