Bagaimana cara menampilkan catatan unik dari hubungan has_many melalui?

106

Saya bertanya-tanya apa cara terbaik untuk menampilkan catatan unik dari has_many, melalui hubungan di Rails3.

Saya punya tiga model:

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

Katakanlah saya ingin mencantumkan semua produk yang dipesan pelanggan di halaman pertunjukan mereka.

Mereka mungkin telah memesan beberapa produk beberapa kali, jadi saya menggunakan counter_cache untuk menampilkan dalam urutan peringkat menurun, berdasarkan jumlah pesanan.

Tapi, jika mereka sudah memesan produk beberapa kali, saya perlu memastikan bahwa setiap produk hanya terdaftar satu kali.

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

berfungsi ketika ada beberapa catatan pesanan untuk suatu produk, tetapi menghasilkan kesalahan jika produk hanya dipesan sekali. (peringkat adalah fungsi pengurutan khusus yang ditentukan di tempat lain)

Alternatif lainnya adalah:

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

Saya tidak yakin bahwa saya menggunakan pendekatan yang benar di sini.

Apakah ada orang lain yang menangani ini? Masalah apa yang Anda hadapi? Di mana saya dapat mengetahui lebih lanjut tentang perbedaan antara .unique! dan DISTINCT ()?

Apa cara terbaik untuk menghasilkan daftar catatan unik melalui has_many, through relationship?

Terima kasih

Andy Harvey
sumber

Jawaban:

236

Sudahkah Anda mencoba menentukan opsi: uniq pada asosiasi has_many:

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

Dari dokumentasi Rails :

:uniq

Jika benar, duplikat akan dihilangkan dari koleksi. Berguna dalam hubungannya dengan: melalui.

UPDATE UNTUK RAILS 4:

Di Rails 4, has_many :products, :through => :orders, :uniq => truesudah tidak digunakan lagi. Sebaliknya, Anda sekarang harus menulis has_many :products, -> { distinct }, through: :orders. Lihat bagian berbeda untuk has_many:: melalui hubungan di dokumentasi Asosiasi ActiveRecord untuk informasi selengkapnya. Terima kasih kepada Kurt Mueller karena telah menunjukkan hal ini dalam komentarnya.

mbreining
sumber
6
Perbedaannya tidak begitu banyak apakah Anda menghapus duplikat dalam model atau pengontrol tetapi Anda menggunakan opsi: uniq pada asosiasi Anda (seperti yang ditunjukkan dalam jawaban saya) atau SQL DISTINCT stmt (misalnya has_many: products,: through =>: orders ,: select => "DISTINCT products. *). Dalam kasus pertama, SEMUA record diambil dan rails menghapus duplikatnya untuk Anda. Dalam kasus selanjutnya, hanya record non-duplikat yang diambil dari db sehingga mungkin menawarkan kinerja yang lebih baik jika Anda memiliki set hasil yang besar.
mbreining
68
Di Rails 4, has_many :products, :through => :orders, :uniq => truesudah tidak digunakan lagi. Sebaliknya, Anda sekarang harus menulis has_many :products, -> { uniq }, through: :orders.
Kurt Mueller
8
Perhatikan bahwa -> {uniq} dalam pengertian ini hanyalah alias untuk -> { different } apidock.com/rails/v4.1.8/ActiveRecord/QueryMethods/uniq Ini terjadi di SQL bukan ruby
engineerDave
5
Jika Anda mendapatkan konflik dengan klausa DISTINCTdan ORDER BY, Anda selalu dapat menggunakanhas_many :products, -> { unscope(:order).distinct }, through: :orders
fagiani
1
terima kasih @fagiani dan jika model Anda memiliki kolom json dan Anda menggunakan psql, itu akan menjadi lebih rumit dan Anda harus melakukan sesuatu seperti has_many :subscribed_locations, -> { unscope(:order).select("DISTINCT ON (locations.id) locations.*") },through: :people_publication_subscription_locations, class_name: 'Location', source: :locationjika tidakrails ActiveRecord::StatementInvalid: PG::UndefinedFunction: ERROR: could not identify an equality operator for type json
ryan2johnson9
43

Perhatikan bahwa uniq: truetelah dihapus dari opsi yang valid untuk has_manyper Rails 4.

Di Rails 4 Anda harus menyediakan ruang lingkup untuk mengkonfigurasi perilaku semacam ini. Cakupan dapat dipasok melalui lambda, seperti:

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

Panduan rails mencakup ini dan cara lain Anda dapat menggunakan cakupan untuk memfilter kueri relasi Anda, gulir ke bawah ke bagian 4.3.3:

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

Yoshiki
sumber
4
Dalam Rails 5.1, saya terus mendapatkan undefined method 'except' for #<Array...dan itu karena saya menggunakan .uniqbukannya.distinct
stevenspiel
5

Anda bisa menggunakan group_by. Misalnya, saya memiliki keranjang belanja galeri foto yang saya ingin item pesanannya diurutkan berdasarkan foto mana (setiap foto dapat dipesan beberapa kali dan dalam ukuran cetakan yang berbeda). Ini kemudian mengembalikan hash dengan produk (foto) sebagai kuncinya dan setiap kali dipesan dapat dicantumkan dalam konteks foto (atau tidak). Dengan menggunakan teknik ini, Anda sebenarnya dapat mengeluarkan riwayat pesanan untuk setiap produk tertentu. Tidak yakin apakah itu membantu Anda dalam konteks ini, tetapi menurut saya itu cukup berguna. Ini kodenya

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

@order_items_by_photo kemudian terlihat seperti ini:

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

Jadi Anda bisa melakukan sesuatu seperti:

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

Kemudian saat Anda mendapatkan ini dalam tampilan Anda, cukup putar melalui sesuatu seperti ini:

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

Dengan cara ini Anda menghindari masalah yang terlihat saat mengembalikan hanya satu produk, karena Anda selalu tahu bahwa itu akan mengembalikan hash dengan produk sebagai kunci dan larik pesanan Anda.

Mungkin berlebihan untuk apa yang Anda coba lakukan, tetapi itu memberi Anda beberapa pilihan bagus (misalnya tanggal dipesan, dll.) Untuk dikerjakan selain kuantitas.

Josh Kovach
sumber
terima kasih atas jawaban mendetailnya. Ini mungkin sedikit lebih dari yang saya butuhkan, tetapi tetap menarik untuk dipelajari (sebenarnya saya dapat menggunakan ini di tempat lain di aplikasi). Apa pendapat Anda tentang kinerja untuk berbagai pendekatan yang disebutkan di halaman ini?
Andy Harvey
2

Di Rails 6 saya membuat ini bekerja dengan sempurna:

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

Saya tidak bisa mendapatkan jawaban lain untuk bekerja.

Sean Mitchell
sumber
Sama pada rel 5.2+
Glenn