Question

I feel like I have read all SO "has_many through" questions but none helped me with my problem.

So I have a standard has_many through setup like this:

class User < ActiveRecord::Base
  has_many :product_associations
  has_many :products, through: :product_associations
end

class ProductAssociation < ActiveRecord::Base
  belongs_to :user
  belongs_to :product
end

class Product < ActiveRecord::Base
  has_many :product_associations
  has_many :users, through: :product_associations
end

IMO, What I want is pretty simple:

Find all users that have a product association to products A, B, and C, no more, no less

So I have a couple of products and want to find all users that are connected to exactly those products (they shouldn't have any other product associations to other products).

This is the best I came up with:

products # the array of products that I want to find all connected users for

User
  .joins(:product_associations)
  .where(product_associations: { product_id: products.map(&:id) })
  .group('products.id')
  .having("COUNT(product_associations.id) = #{products.count}")

It doesn't work though, it also returns users connected to more products.

I also toyed around with merging scopes but didn't get any result.

All hints appreciated! :)

Was it helpful?

Solution

select * from users
join product_associations on product_associations.user_id = users.id
where product_associations.product_id in (2,3)
and not exists (
  select *
  from product_associations AS pa
  where pa.user_id = users.id
  and pa.product_id not in (2,3)
)
group by product_associations.user_id
having count(product_associations.product_id) = 2

It does two things, find users with: 1) all the product associations and 2) no other product associations.

Sqlfiddle example: http://sqlfiddle.com/#!2/aee8e/5

It can be Railsified™ (somewhat) in to:

User.joins(:product_associations)
  .where(product_associations: { product_id: products })
  .where("not exists (select *
    from product_associations AS pa
    where pa.user_id = users.id
    and pa.product_id not in (?)
  )", products.pluck(:id))
  .group('product_associations.user_id')
  .having('count(product_associations.product_id) = ?', products.count)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top