Как отображать уникальные записи из отношения 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
Таким образом, вы избегаете проблемы, возникающей при возврате только одного продукта, поскольку вы всегда знаете, что он вернет хэш с продуктом в качестве ключа и массива ваших заказов.
Это может быть излишним для того, что вы пытаетесь сделать, но это дает вам несколько хороших вариантов (например, упорядоченных дат и т.д.) для работы в дополнение к количеству.