web-dev-qa-db-ja.com

has_many throughリレーションシップから一意のレコードを表示する方法は?

Rails3のリレーションシップを使用して、has_manyから一意のレコードを表示する最善の方法は何だろうと思っています。

3つのモデルがあります。

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、リレーションシップを通じて一意のレコードのリストを生成する最良の方法は何ですか?

ありがとう

100
Andy Harvey

Has_manyアソシエーションで:uniqオプションを指定しようとしましたか?

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

から Rails documentation

:uniq

Trueの場合、重複はコレクションから省略されます。 :throughと組み合わせて使用​​すると便利です。

UPDATE FOR Rails 4:

In Rails 4、has_many :products, :through => :orders, :uniq => trueは非推奨です。代わりに、has_many :products, -> { distinct }, through: :orders。詳細については、 ActiveRecord Associationsドキュメントのhas_many::through関係の個別セクション を参照してください。彼のコメントでこれを指摘してくれたカート・ミューラーに感謝します。

224
mbreining

Rails 4の時点で、uniq: trueの有効なオプションからhas_manyが削除されていることに注意してください。

Rails 4では、この種の動作を設定するためにスコープを提供する必要があります。スコープは次のようにラムダを介して提供できます。

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

Railsガイドは、これと、スコープを使用してリレーションのクエリをフィルタリングする他の方法をカバーしています。セクション4.3.3までスクロールダウンします。

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

41
Yoshiki

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

これにより、1つの製品のみを返すときに見られる問題を回避できます。これは、製品がキーおよび注文の配列であるハッシュを返すことが常にわかっているためです。

あなたがしようとしていることはやりすぎかもしれませんが、数量に加えて作業するニースのオプション(つまり、注文日など)を提供します。

5
Josh Kovach