How to display unique records from a has_many through relationship?

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


Solution 1:

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.

Solution 2:

Note that uniq: true has been removed from the valid options for has_many as of Rails 4.

In Rails 4 you have to supply a scope to configure this kind of behavior. Scopes can be supplied through lambdas, like so:

has_many :products, -> { uniq }, :through => :orders

The rails guide covers this and other ways you can use scopes to filter your relation's queries, scroll down to section 4.3.3:

http://guides.rubyonrails.org/association_basics.html#has-many-association-reference