関係を通じてhas_manyからの一意のレコードを表示する方法は?


106

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

ありがとう

回答:


235

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

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

Railsのドキュメントから:

:uniq

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

レール4の更新:

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


6
モデルまたはコントローラーで重複を削除するかどうかの違いはそれほどではありませんが、(私の回答に示されているように)関連付けで:uniqオプションを使用するか、SQL DISTINCT stmt(eg has_many:products、:through =>:orders)を使用します、:select => "DISTINCT products。*)。最初のケースでは、すべてのレコードがフェッチされ、railsが重複を削除します。後者の場合、非重複レコードのみがデータベースからフェッチされるため、パフォーマンスが向上する可能性があります結果セットが大きい場合
2011年

68
Rails 4ではhas_many :products, :through => :orders, :uniq => true非推奨です。代わりに、ここでを書く必要がありますhas_many :products, -> { uniq }, through: :orders
カートミュラー

8
この意味での-> {uniq}は、単に-> {distinct} apidock.com/rails/v4.1.8/ActiveRecord/QueryMethods/uniqのエイリアスにすぎないことに注意してください。SQLではルビーではありません
engineerDave

5
DISTINCTand ORDER BY句と競合する場合は、いつでも使用できますhas_many :products, -> { unscope(:order).distinct }, through: :orders
fagiani

1
おかげ@fagianiとあなたのモデルは、JSONの列を持っており、あなたはそれがより複雑になるまだpsqlを使用して、あなたのような何かしなければならない場合はhas_many :subscribed_locations, -> { unscope(:order).select("DISTINCT ON (locations.id) locations.*") },through: :people_publication_subscription_locations, class_name: 'Location', source: :location、あなたが得るそれ以外の場合はrails ActiveRecord::StatementInvalid: PG::UndefinedFunction: ERROR: could not identify an equality operator for type json
ryan2johnson9

43

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


4
Rails 5.1では、私はundefined method 'except' for #<Array....uniq.distinct
次々

5

使用できます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つの製品のみを返すときに見られる問題を回避できます。これは、製品をキーとして、注文の配列としてハッシュを返すことを常に知っているためです。

それはあなたがやろうとしていることにはやり過ぎかもしれませんが、数量に加えていくつかの素晴らしいオプション(つまり、注文した日付など)を扱うことができます。


詳細な回答に感謝します。これは私が必要とするものより少し多いかもしれませんが、それにもかかわらずそれから学ぶのは興味深いです(実際に私はこれをアプリの他の場所で使用するかもしれません)。このページで言及されているさまざまなアプローチのパフォーマンスについてどう思いますか?
アンディハーヴェイ、

2

Rails 6では、これを完全に機能させることができました。

  has_many :regions, -> { order(:name).distinct }, through: :sites

他の答えはどれもうまくいきませんでした。


レール5.2+と同じ
Glenn
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.