Bir has_many'den ilişki yoluyla benzersiz kayıtlar nasıl görüntülenir?


106

Bir has_many'den benzersiz kayıtları Rails3'teki ilişki aracılığıyla görüntülemenin en iyi yolunun ne olduğunu merak ediyorum.

Üç modelim var:

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

Diyelim ki bir müşterinin sipariş ettiği tüm ürünleri gösteri sayfasında listelemek istiyorum.

Bazı ürünleri birden çok kez sipariş etmiş olabilirler, bu nedenle sipariş sayısına bağlı olarak azalan sırada görüntülemek için counter_cache kullanıyorum.

Ancak, bir ürünü birden çok kez sipariş etmişlerse, her ürünün yalnızca bir kez listelendiğinden emin olmam gerekir.

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

bir ürün için birden fazla sipariş kaydı olduğunda çalışır, ancak bir ürün yalnızca bir kez sipariş edilmişse bir hata oluşturur. (sıralama, başka yerde tanımlanan özel sıralama işlevidir)

Diğer bir alternatif ise:

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

Burada doğru yaklaşımda olduğuma emin değilim.

Bununla başka kimse uğraştı mı? Hangi sorunlarla karşılaştınız? .Unique! Arasındaki fark hakkında daha fazla bilgiyi nerede bulabilirim! ve DISTINCT ()?

Bir has_many aracılığıyla ilişki yoluyla benzersiz kayıtların bir listesini oluşturmanın en iyi yolu nedir?

Teşekkürler

Yanıtlar:


237

Has_many ilişkilendirmesinde: uniq seçeneğini belirtmeyi denediniz mi:

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

Gönderen Raylar belgeler :

:uniq

Doğruysa, kopyalar koleksiyondan çıkarılır. İle birlikte kullanışlıdır: içinden.

RAILS 4 İÇİN GÜNCELLEME:

Rails 4'te has_many :products, :through => :orders, :uniq => truekullanımdan kaldırılmıştır. Bunun yerine, şimdi yazmalısınız has_many :products, -> { distinct }, through: :orders. Daha fazla bilgi için ActiveRecord İlişkileri belgelerinde has_many:: ile ilgili ayrı bölüme bakın . Kurt Mueller'e yorumunda buna işaret ettiği için teşekkürler.


6
Aradaki fark, model veya kontrolördeki kopyaları kaldırmanızdan ziyade, ilişkilendirmenizde: uniq seçeneğini (cevabımda gösterildiği gibi) veya SQL DISTINCT komutunu (örn. Has_many: products,: through =>: orders ,: select => "DISTINCT ürünleri. *). İlk durumda, TÜM kayıtlar alınır ve rails kopyaları sizin için kaldırır. Daha sonraki durumda, daha iyi performans sunabilmesi için db'den yalnızca yinelenmemiş kayıtlar alınır büyük bir sonuç
kümeniz

68
Rails 4'te has_many :products, :through => :orders, :uniq => truekullanımdan kaldırılmıştır. Bunun yerine, şimdi yazmalısınız has_many :products, -> { uniq }, through: :orders.
Kurt Mueller

8
> Bu anlamda {uniq} için sadece bir takma addır - - Not> {ayrı} apidock.com/rails/v4.1.8/ActiveRecord/QueryMethods/uniq Değil yakut SQL oluşur
engineerDave

5
Eğer bir ile çatışma alırsanız DISTINCTve ORDER BYcümlecikleri, her zaman kullanabilirsinizhas_many :products, -> { unscope(:order).distinct }, through: :orders
fagiani

1
teşekkürler @fagiani ve eğer modelinizde bir json sütunu varsa ve psql kullanıyorsanız, daha karmaşık hale gelir ve has_many :subscribed_locations, -> { unscope(:order).select("DISTINCT ON (locations.id) locations.*") },through: :people_publication_subscription_locations, class_name: 'Location', source: :locationaksi takdirde elde rails ActiveRecord::StatementInvalid: PG::UndefinedFunction: ERROR: could not identify an equality operator for type json
edeceğiniz

43

Not uniq: trueiçin geçerli seçenekler kaldırıldı has_manyRaylar 4 itibarıyla.

Rails 4'te bu tür davranışları yapılandırmak için bir kapsam sağlamanız gerekir. Kapsamlar aşağıdaki gibi lambdalar aracılığıyla sağlanabilir:

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

Raylar kılavuzu, bunu ve ilişkinizin sorgularını filtrelemek için kapsamları kullanabileceğiniz diğer yolları kapsar, bölüm 4.3.3'e gidin:

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


4
Rails 5.1'de, almaya devam ettim undefined method 'except' for #<Array...ve bunun nedeni .uniqyerine.distinct
stevenspiel

5

Kullanabilirsin group_by. Örneğin, sipariş öğelerinin hangi fotoğrafa göre sıralanmasını istediğim bir fotoğraf galerisi alışveriş sepetim var (her bir fotoğraf birden çok kez ve farklı boyutta baskılarla sipariş edilebilir). Bu daha sonra anahtar olarak ürün (fotoğraf) ile bir karma döndürür ve her sipariş edildiğinde fotoğraf bağlamında listelenebilir (veya listelenmeyebilir). Bu tekniği kullanarak, her bir ürün için bir sipariş geçmişi çıktı alabilirsiniz. Bu bağlamda size yardımcı olup olmadığından emin değilim, ancak oldukça yararlı buldum. İşte kod

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

@order_items_by_photo sonra şuna benzer:

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

Böylece şöyle bir şey yapabilirsiniz:

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

Sonra bunu kendi görüşünüze aldığınızda, şuna benzer bir döngüden geçin:

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

Bu şekilde, yalnızca bir ürünü iade ederken görülen sorundan kaçınırsınız, çünkü her zaman anahtar olarak bir ürün ve siparişlerinizin bir dizisi ile bir karma iade edeceğini bilirsiniz.

Yapmaya çalıştığınız şey için aşırı olabilir, ancak miktara ek olarak çalışmak için size bazı güzel seçenekler (yani sipariş edilen tarihler vb.) Sunar.


Detaylı cevap için teşekkürler. Bu ihtiyacım olandan biraz daha fazla olabilir, ancak yine de öğrenmesi ilginç (aslında bunu uygulamanın başka bir yerinde kullanabilirim). Bu sayfada bahsedilen çeşitli yaklaşımlar için performansla ilgili düşünceleriniz nelerdir?
Andy Harvey

2

Rails 6'da bunu mükemmel bir şekilde çalıştırdım:

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

Çalışmak için diğer cevapların hiçbirini alamadım.


Aynı raylarda 5.2+
Glenn
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.