【问题标题】:how to perform a somewhat complicated ActiveRecord scope query for a polymorphic association?如何为多态关联执行有点复杂的 ActiveRecord 范围查询?
【发布时间】:2011-09-30 08:32:39
【问题描述】:

所以,我有一个多态的Notification 模型,我希望能够过滤掉属于notifiable_type Comment 的通知,其中comment.user == current_user。换句话说,我想要所有的通知记录——除了那些引用当前用户创建的 cmets 的记录。

class Notification

  belongs_to :notifiable, :polymorphic => true

  scope :relevant, lambda { |user_id|
    find(:all, :conditions => [
      "notifiable_type != 'Comment' OR (notifiable_type = 'Comment' AND " <<
        "comments.user_id != ?)",
      user_id ],
      :include => :comments
    )
  }

end

我不明白我需要做什么才能访问 cmets?我需要告诉 ActiveRecord 在notifiable_id 上加入评论模型。

【问题讨论】:

  • 你从上面得到了什么结果?例如。如果你打电话给Notification.relevant.first.comments,你会得到什么?
  • 一个错误..“ActiveRecord::ConfigurationError: Association named 'cmets' is not found;也许你拼错了?”..因为通知没有 cmets..它有一个“通知”可以是评论,也可以是其他模型实例。
  • 但是你对:include =&gt; :comments说你不应该说:include =&gt; :notifiables或类似的东西吗?评论是否需要通知?
  • with :include => :notifiable, 我得到:“ActiveRecord::EagerLoadPolymorphicError: Can not eagerly load the polymorphic association :notifiable”.. 因此我的问题和我的问题..

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


【解决方案1】:

首先,不推荐使用带参数的 lambda 作用域。改用类方法:

class Notification
  belongs_to :notifiable, polymorphic: true

  def self.relevant(user_id)
    # ...
  end
end

我通常将作用域函数移到它们自己的模块中,但您可以将其留在那里。

接下来,find(:all) 已弃用,:conditions 也是如此。我们现在使用ActiveRelation queries

不幸的是,ActiveRecord::Relation API 不够强大,无法满足您的需求,因此我们必须改为使用 ARel。有点棘手,但出于安全原因,您绝对不想进行字符串替换。

class Notification
  belongs_to :notifiable, polymorphic: true

  def self.relevant(user_id)
    n, c = arel_table, Comment.arel_table
    predicate = n.join(c).on(n[:notifiable_id].eq(c[:id]))

    joins( predicate.join_sql ).
    where{ ( notifiable_type != 'Comment' ) | 
      (( notifiable_type == 'Comment' ) & ( comments.user_id == my{user_id} ))
    }
  end
end

我在这里使用了 ARel 和 Squeel 的组合。 Squeel 太好了,应该是 Rails 的核心功能。我尝试在没有 Squeel 的情况下编写 where 子句,但太难了,我放弃了。

如果没有你的项目,很难测试这样的东西,但希望这至少能让你更接近。

【讨论】:

    【解决方案2】:

    哎呀,你的代码有:include =&gt; :comments,复数,这让我很失望。这个怎么样?

    class Notification
      belongs_to :notifiable, :polymorphic => true
    
      scope :relevant, lambda { |user_id|
        find(:all, :conditions => [
          "notifiable_type != 'Comment' OR (notifiable_type = 'Comment' AND " <<
            "comments.user_id != ?)",
          user_id ],
          :include => :notifiable
        )
      }
    
    end
    

    ...然后Notification.relevant.first.notifiable 应该可以工作。来自文档:

    多态关联支持急切加载。

    class Address < ActiveRecord::Base
      belongs_to :addressable, :polymorphic => true
    end
    

    尝试预先加载可寻址模型的调用

    Address.find(:all, :include => :addressable)
    

    这将执行一个查询以加载地址并加载 每个可寻址类型具有一个查询的可寻址对象。例如,如果所有 可寻址对象是 Person 或 Company 类,然后总共 将执行 3 个查询。要加载的可寻址类型列表是 确定在加载的地址的背面。 不支持 如果 Active Record 必须回退到之前的实现 急切加载并会提高 ActiveRecord::EagerLoadPolymorphicError。原因是 父模型的类型是一个列值,所以它对应的表名 不能放在该查询的 FROM/JOIN 子句中。

    (强调我的。)

    【讨论】:

    • 我刚刚尝试过... Notification.relevant(123) 给了我:“ActiveRecord::EagerLoadPolymorphicError: Can not eagerly load the polymorphic association :notifiable”... 然而 Notification.find(:全部,:include => :notifiable) 工作正常。
    • 做了一些更多的实验,当试图在条件中解决“cmets.xxx”时,错误就出现了。当我把那部分拿出来时,它工作得很好。我需要一种方法来告诉 ActiveRecord 可通知的是表 cmets(并加入它)...
    • @patrick 是的,正在努力。 Arel 谓词让我的大脑受伤。在这种情况下,安装 gem 几乎是必须的。
    【解决方案3】:

    这就是我所采取的......我仍然希望有人能告诉我一个更好的方法。

    Notification.find_by_sql( "SELECT * from Notifications " <<
      "INNER JOIN comments ON notifiable_id = comments.id " <<
      "WHERE notifiable_type != 'Comment' " <<
        "OR (notifiable_type = 'Comment' AND comments.user_id = '#{user_id}')"
    )
    

    【讨论】:

    • 我现在也遇到了这种问题,但我问的方式不同。 stackoverflow.com/questions/12596440/…。但是你有同样的问题,如果已经有很多表(通知),你会怎么做?你会对查询进行 n 连接吗?
    猜你喜欢
    • 1970-01-01
    • 2019-07-28
    • 1970-01-01
    • 2010-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多