开发者

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.

0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜