如何为多态关联执行有点复杂的ActiveRecord范围查询?

时间:2011-09-28 19:11:39

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

所以,我有一个多态的Notification模型,我希望能够过滤出notifiable_type Comment comment.user == current_user的通知。换句话说,我想要所有通知记录 - 除了那些引用当前用户发表的评论的记录。

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

我不明白我需要做些什么才能获得评论?我需要告诉ActiveRecord外部加入notifiable_id上的评论模型。

3 个答案:

答案 0 :(得分:5)

首先,不推荐使用带参数的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子句,但是我放弃了这么困难。

很难在没有你的项目的情况下测试这样的东西,但希望这至少可以让你更接近。

答案 1 :(得分:0)

哎呀,你的代码有:include => :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子句中。

(强调我的。)

答案 2 :(得分:0)

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

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}')"
)