【问题标题】: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)进行过滤?

【问题讨论】:

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


    【解决方案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
    

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

    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 的表。

    我相信这比在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 子句中查询多态关联(这是有道理的,因为多态关联代表多个表。)

      【讨论】:

      【解决方案5】:

      这对我有用

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-03-16
        • 1970-01-01
        • 2012-08-08
        • 2017-12-26
        • 2019-07-13
        • 2010-12-01
        • 2017-08-28
        相关资源
        最近更新 更多