开发者

how to perform a somewhat complicated ActiveRecord scope query for a polymorphic association?

开发者 https://www.devze.com 2023-04-08 21:34 出处:网络
S开发者_StackOverflow中文版o, I have a Notification model that is polymorphic, and I want to be able to filter out notifications that are of a notifiable_type Comment where comment.user == current_use

S开发者_StackOverflow中文版o, I have a Notification model that is polymorphic, and I want to be able to filter out notifications that are of a notifiable_type Comment where comment.user == current_user. In other words, I want all notification records--except for ones referring to comments that were made by the 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

What I don't understand is what I need to do to get access to comments? I need to tell ActiveRecord to outer join the comment model on notifiable_id.


First, lambda scopes with parameters are deprecated. Use a class method instead:

class Notification
  belongs_to :notifiable, polymorphic: true

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

I usually move scope functions into their own module, but you can leave it there.

Next, find(:all) is deprecated, as is :conditions. We use ActiveRelation queries now.

Unfortunately, the ActiveRecord::Relation API isn't quite robust enough to do what you need, so we'll have to drop down to ARel instead. A little bit tricky, but you definitely don't want to be doing string substitution for security reasons.

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

I'm using a combination of ARel and Squeel here. Squeel is so good it should be a Rails core feature. I tried writing that where clause without Squeel, but it was so difficult I gave up.

Hard to test something like this without your project handy, but hopefully that should at least get you closer.


Oops, your code has :include => :comments, plural, which threw me off. How about this?

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

...then Notification.relevant.first.notifiable should work. From the docs:

Eager loading is supported with polymorphic associations.

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

A call that tries to eager load the addressable model

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

This will execute one query to load the addresses and load the addressables with one query per addressable type. For example if all the addressables are either of class Person or Company then a total of 3 queries will be executed. The list of addressable types to load is determined on the back of the addresses loaded. This is not supported if Active Record has to fallback to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent model’s type is a column value so its corresponding table name cannot be put in the FROM/JOIN clauses of that query.

(Emphasis mine.)


This is what I've resorted to..... I am still hoping someone can show me a better way.

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

精彩评论

暂无评论...
验证码 换一张
取 消