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}')"
)
精彩评论