How to display unique entries from a has_many relationship?

I am wondering what is the best way to display unique entries from has_many, through relationships 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 

Suppose I want to list all the products ordered by a customer on their display page.

Perhaps they ordered several products several times, so I use counter_cache to display in descending order of rank depending on the number of orders.

But, if they ordered the product several times, I need to make sure that each product is listed only once.

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

works when there are several order records for a product, but generates an error if the product was ordered only once. (ranking is a custom sorting function defined elsewhere)

Another variant:

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

I am not sure that I am here right.

Has anyone else done this? What problems have you encountered? Where can I find out more about the difference between .unique! and DISTINCT ()?

What is the best way to create a list of unique entries through has_many through relationships?

thank

+84
ruby-on-rails ruby-on-rails-3 unique distinct
May 01 '11 at 5:19
source share
3 answers

You tried to specify the: uniq option in the has_many association:

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

From the Rails documentation :

:uniq

If true, duplicates will be excluded from the collection. Useful in combination with: through.

UPDATE FOR RAILS 4:

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

+190
May 01 '11 at 5:30
source share

Note that uniq: true been removed from valid has_many parameters with Rails 4.

In Rails 4, you must specify an area to customize this behavior. Areas can be supplied via lambda, for example:

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

The guides guide covers this and other ways to use areas to filter your relationship queries, scroll down to section 4.3.3:

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

+36
Aug 28 '14 at 19:47
source share

You can use group_by . For example, I have a basket with a photo gallery for which I want to sort order items with which I can take pictures (each photo can be ordered several times and printed in different sizes). Then it returns a hash with the product (photo) as a key and each time it was ordered, it can be indicated in the context of the photo (or not). Using this method, you can actually display the order history for each given product. Not sure if this is useful to you in this context, but I found it quite useful. Here is the code

 OrdersController#show @order = Order.find(params[:id]) @order_items_by_photo = @order.order_items.group_by(&:photo) 

@order_items_by_photo then looks something like this:

 => {#<Photo id: 128>=>[#<OrderItem id: 2, photo_id: 128>, #<OrderItem id: 19, photo_id: 128>] 

So you can do something like:

 @orders_by_product = @user.orders.group_by(&:product) 

Then, when you get this in your view, just skip something like this:

 - for product, orders in @user.orders_by_product - "#{product.name}: #{orders.size}" - for order in orders - output_order_details 

Thus, you avoid the problem that occurs when returning only one product, since you always know that it will return a hash with the product as the key and array of your orders.

This may be redundant for what you are trying to do, but it gives you some good options (e.g. ordered dates, etc.) to work in addition to quantity.

+5
May 01 '11 at 6:35 a.m.
source share



All Articles