Как отображать уникальные записи из отношения has_many?

Мне интересно, как лучше всего отображать уникальные записи из has_many, через отношения в Rails3.

У меня есть три модели:

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

Предположим, что я хочу перечислить все продукты, заказанные клиентом на их странице показа.

Возможно, они заказали несколько продуктов несколько раз, поэтому я использую counter_cache для отображения в порядке убывания рангов в зависимости от количества заказов.

Но, если они заказали продукт несколько раз, мне нужно убедиться, что каждый товар указан только один раз.

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

работает, когда есть несколько записей заказов для продукта, но генерирует ошибку, если продукт был заказан только один раз. (ранжирование - это функция пользовательского сортировки, определенная в другом месте)

Другой вариант:

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

Я не уверен, что я нахожусь здесь правильно.

Кто-нибудь еще занимался этим? С какими проблемами вы столкнулись? Где я могу узнать больше о различии между .unique! и DISTINCT()?

Каков наилучший способ создания списка уникальных записей через has_many через отношения?

Спасибо

Ответ 1

Вы пытались указать опцию: uniq в ассоциации has_many:

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

От документация Rails:

:uniq

Если true, дубликаты будут исключены из коллекции. Полезно в сочетании с: через.

ОБНОВЛЕНИЕ ДЛЯ RAILS 4:

В Rails 4 has_many :products, :through => :orders, :uniq => true устарел. Вместо этого вы должны написать has_many :products, -> { distinct }, through: :orders. Дополнительную информацию см. В разделе для has_many:: через отношения в документации ActiveRecord Association. Спасибо Курту Мюллеру за указание на это в его комментарии.

Ответ 2

Обратите внимание, что uniq: true было удалено из допустимых параметров has_many с Rails 4.

В Rails 4 вы должны указать область для настройки такого поведения. Области могут поставляться через лямбда, например:

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

Руководство по направляющим покрывает этот и другие способы использования областей для фильтрации ваших запросов отношения, прокрутите вниз до раздела 4.3.3:

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

Ответ 3

Вы можете использовать group_by. Например, у меня есть корзина с фотогалереей, для которой я хочу сортировать элементы заказа, с помощью которых можно фотографировать (каждую фотографию можно заказывать несколько раз и печатать в разных размерах). Затем он возвращает хеш с продуктом (фото) в качестве ключа и каждый раз, когда он был заказан, может быть указан в контексте фотографии (или нет). Используя этот метод, вы можете фактически выводить историю заказов для каждого данного продукта. Не уверен, что это полезно для вас в этом контексте, но я нашел его весьма полезным. Здесь код

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

@order_items_by_photo затем выглядит примерно так:

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

Итак, вы можете сделать что-то вроде:

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

Затем, когда вы получите это в своем представлении, просто пропустите что-то вроде этого:

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

Таким образом, вы избегаете проблемы, возникающей при возврате только одного продукта, поскольку вы всегда знаете, что он вернет хэш с продуктом в качестве ключа и массива ваших заказов.

Это может быть излишним для того, что вы пытаетесь сделать, но это дает вам несколько хороших вариантов (например, упорядоченных дат и т.д.) для работы в дополнение к количеству.