How to display unique records from a has_many through relationship?

Andy Harvey picture Andy Harvey · May 1, 2011 · Viewed 44k times · Source

I'm wondering what is the best way to display unique records from a has_many, through relationship in Rails3.

I have three models:

class User < ActiveRecord::Base
    has_many :orders
    has_many :products, :through => :orders
end

class Products < ActiveRecord::Base
    has_many :orders
    has_many :users, :through => :orders
end

class Order < ActiveRecord::Base
    belongs_to :user, :counter_cache => true 
    belongs_to :product, :counter_cache => true 
end

Lets say I want to list all the products a customer has ordered on their show page.

They may have ordered some products multiple times, so I'm using counter_cache to display in descending rank order, based on the number of orders.

But, if they have ordered a product multiple times, I need to ensure that each product is only listed once.

@products = @user.products.ranked(:limit => 10).uniq!

works when there are multiple order records for a product, but generates an error if a product has only been ordered once. (ranked is custom sort function defined elsewhere)

Another alternative is:

@products = @user.products.ranked(:limit => 10, :select => "DISTINCT(ID)")

I'm not confident that I'm on the right approach here.

Has anyone else tackled this? What issues did you come up against? Where can I find out more about the difference between .unique! and DISTINCT()?

What is the best way to generate a list of unique records through a has_many, through relationship?

Thanks

Answer

mbreining picture mbreining · May 1, 2011

Have you tried to specify the :uniq option on the has_many association:

has_many :products, :through => :orders, :uniq => true

From the Rails documentation:

:uniq

If true, duplicates will be omitted from the collection. Useful in conjunction with :through.

UPDATE FOR RAILS 4:

In Rails 4, has_many :products, :through => :orders, :uniq => true is deprecated. Instead, you should now write has_many :products, -> { distinct }, through: :orders. See the distinct section for has_many: :through relationships on the ActiveRecord Associations documentation for more information. Thanks to Kurt Mueller for pointing this out in his comment.