Rails 3: has_many :uniq issue
Here is a sample model setup (barebone Rails 3.0.5):
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :user
end
class User < ActiveRecord::Base
has_many :comments
has_many :commented_posts, through: :comments, source: :post, uniq: true
end
Now the following works correctly:
ruby-1.9.2-p0 > user.commented_posts.count
SQL (0.2ms) SELECT COUNT(DISTINCT "posts".id) FROM "posts" INNER JOIN "comments" ON "posts".id = "comments".post_id WHERE (("comments".user_id = 1))
=开发者_高级运维> 1
But adding condition makes active record 'forget' about uniq: true
bit:
ruby-1.9.2-p0 > user.commented_posts.where("posts.id != 42").count
SQL (0.2ms) SELECT COUNT(*) FROM "posts" INNER JOIN "comments" ON "posts".id = "comments".post_id WHERE (("comments".user_id = 1)) AND (posts.id != 42)
=> 2
Bug? Or what am I missing?
edit:
all
works:
ruby-1.9.2-p0 > user.commented_posts.where("posts.id != 42").all
Post Load (0.3ms) SELECT DISTINCT "posts".* FROM "posts" INNER JOIN "comments" ON "posts".id = "comments".post_id WHERE (("comments".user_id = 1)) AND (posts.id != 42)
=> [#<Post id: 1, created_at: "2011-03-07 12:17:30", updated_at: "2011-03-07 12:17:30">]
explicit uniq
too:
ruby-1.9.2-p0 > user.commented_posts.where("posts.id != 42").uniq.count
Post Load (0.2ms) SELECT DISTINCT "posts".* FROM "posts" INNER JOIN "comments" ON "posts".id = "comments".post_id WHERE (("comments".user_id = 1)) AND (posts.id != 42)
=> 1
edit 2
Indeed bug in Rails. I submitted a patch. Please upvote it so it gets through sooner. https://github.com/rails/rails/pull/2924#issuecomment-3317185
I've run into this as well on both 3.0.9 and 3.0.10. I consider it an arel bug, although there might be some reason as to why it behaves this way.
I tried overriding count on the join method...
has_many :commented_posts, through: :comments, source: :post, uniq: true do
def count(column_name = nil, opts = {})
super(column_name || 'users.id', opts.reverse_merge(distinct: true))
end
end
But arel ignores the count method if a condition is present. This is why I think it's a bug.
As a hacking solution, I'm using @comments.count('users.id', distinct: true) to force arel to behave in situations where @comments might have a condition attached in the controller.
I think that the explicit uniq is causing the count to occur from the array in memory rather than doing a count in the database. You can tell this is true from the SELECT DISTINCT rather than SELECT COUNT DISTINCT. This will pull all the entries from the DB into rails and then count them.
It is possible to get the DB to do this by passing a couple of parameters to count
collection.count(:id, :distinct => true) will cause the correct SQL code to be generated.
精彩评论