Làm cách nào để hiển thị các bản ghi duy nhất từ ​​has_many thông qua mối quan hệ?


106

Tôi đang tự hỏi đâu là cách tốt nhất để hiển thị các bản ghi duy nhất từ ​​has_many, thông qua mối quan hệ trong Rails3.

Tôi có ba mô hình:

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

Giả sử tôi muốn liệt kê tất cả các sản phẩm mà khách hàng đã đặt hàng trên trang trưng bày của họ.

Họ có thể đã đặt hàng một số sản phẩm nhiều lần, vì vậy tôi đang sử dụng counter_cache để hiển thị theo thứ tự xếp hạng giảm dần, dựa trên số lượng đơn đặt hàng.

Nhưng, nếu họ đã đặt một sản phẩm nhiều lần, tôi cần đảm bảo rằng mỗi sản phẩm chỉ được liệt kê một lần.

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

hoạt động khi có nhiều bản ghi đơn hàng cho một sản phẩm, nhưng tạo ra lỗi nếu một sản phẩm chỉ được đặt hàng một lần. (xếp hạng là chức năng sắp xếp tùy chỉnh được xác định ở nơi khác)

Một thay thế khác là:

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

Tôi không tự tin rằng tôi đang đi đúng hướng ở đây.

Có ai khác đã giải quyết điều này? Bạn đã đưa ra những vấn đề gì? Tôi có thể tìm hiểu thêm về sự khác biệt giữa .unique ở đâu! và DISTINCT ()?

Cách tốt nhất để tạo danh sách các bản ghi duy nhất thông qua has_many, thông qua mối quan hệ là gì?

Cảm ơn

Câu trả lời:


235

Bạn đã thử chỉ định tùy chọn: uniq trên liên kết has_many chưa:

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

Từ tài liệu Rails :

:uniq

Nếu đúng, các bản sao sẽ bị loại bỏ khỏi bộ sưu tập. Hữu ích khi kết hợp với: qua.

CẬP NHẬT CHO RAILS 4:

Trong Rails 4, has_many :products, :through => :orders, :uniq => truekhông được dùng nữa. Thay vào đó, bây giờ bạn nên viết has_many :products, -> { distinct }, through: :orders. Xem phần riêng biệt cho has_many:: thông qua các mối quan hệ trên tài liệu ActiveRecord Associations để biết thêm thông tin. Cảm ơn Kurt Mueller đã chỉ ra điều này trong bình luận của anh ấy.


6
Sự khác biệt không quá nhiều cho dù bạn xóa các bản sao trong mô hình hay bộ điều khiển mà là bạn sử dụng tùy chọn: uniq trên liên kết của bạn (như được hiển thị trong câu trả lời của tôi) hoặc SQL DISTINCT stmt (ví dụ: has_many: products,: through =>: order ,: select => "DISTINCT products. *). Trong trường hợp đầu tiên, TẤT CẢ các bản ghi được tìm nạp và rails sẽ loại bỏ các bản sao cho bạn. Trong trường hợp sau, chỉ các bản ghi không trùng lặp mới được tìm nạp từ db để nó có thể mang lại hiệu suất tốt hơn nếu bạn có một tập hợp kết quả lớn.
mbreining

68
Trong Rails 4, has_many :products, :through => :orders, :uniq => truekhông được dùng nữa. Thay vào đó, bây giờ bạn nên viết has_many :products, -> { uniq }, through: :orders.
Kurt Mueller

8
Lưu ý rằng -> {} uniq theo nghĩa này chỉ là một bí danh cho -> {} biệt apidock.com/rails/v4.1.8/ActiveRecord/QueryMethods/uniq Nó xảy ra trong SQL không ruby
engineerDave

5
Nếu bạn có xung đột với DISTINCTORDER BYcác điều khoản, bạn luôn có thể sử dụnghas_many :products, -> { unscope(:order).distinct }, through: :orders
fagiani 22/12/16

1
nhờ @fagiani và nếu mô hình của bạn có một cột json và bạn sử dụng psql nó trở nên phức tạp hơn được nêu ra và bạn phải làm một cái gì đó giống như has_many :subscribed_locations, -> { unscope(:order).select("DISTINCT ON (locations.id) locations.*") },through: :people_publication_subscription_locations, class_name: 'Location', source: :locationnếu không bạn có đượcrails ActiveRecord::StatementInvalid: PG::UndefinedFunction: ERROR: could not identify an equality operator for type json
ryan2johnson9

43

Lưu ý rằng uniq: trueđã bị xóa khỏi các tùy chọn hợp lệ has_manykể từ Rails 4.

Trong Rails 4, bạn phải cung cấp một phạm vi để định cấu hình loại hành vi này. Phạm vi có thể được cung cấp thông qua lambdas, như vậy:

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

Hướng dẫn đường ray bao gồm điều này và các cách khác mà bạn có thể sử dụng phạm vi để lọc các truy vấn của mối quan hệ của mình, cuộn xuống phần 4.3.3:

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


4
Trong Rails 5.1, tôi vẫn tiếp tục nhận được undefined method 'except' for #<Array...và đó là vì tôi đã sử dụng .uniqthay vì.distinct
stevenspiel

5

Bạn có thể sử dụng group_by. Ví dụ: tôi có một giỏ hàng thư viện ảnh mà tôi muốn các mục đặt hàng được sắp xếp theo ảnh nào (mỗi ảnh có thể được đặt hàng nhiều lần và in kích thước khác nhau). Sau đó, điều này sẽ trả về một hàm băm với sản phẩm (ảnh) làm khóa và mỗi lần đặt hàng sản phẩm có thể được liệt kê theo ngữ cảnh của ảnh (hoặc không). Sử dụng kỹ thuật này, bạn thực sự có thể xuất lịch sử đặt hàng cho từng sản phẩm nhất định. Không chắc liệu điều đó có hữu ích cho bạn trong bối cảnh này hay không, nhưng tôi thấy nó khá hữu ích. Đây là mã

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

@order_items_by_photo sau đó trông giống như sau:

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

Vì vậy, bạn có thể làm điều gì đó như:

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

Sau đó, khi bạn nhận được điều này trong chế độ xem của mình, chỉ cần lặp qua một cái gì đó như sau:

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

Bằng cách này, bạn sẽ tránh được vấn đề gặp phải khi chỉ trả lại một sản phẩm, vì bạn luôn biết rằng nó sẽ trả về một hàm băm với một sản phẩm làm khóa và một loạt các đơn đặt hàng của bạn.

Nó có thể là quá mức cần thiết cho những gì bạn đang cố gắng làm, nhưng nó cung cấp cho bạn một số tùy chọn tốt (ví dụ như ngày đặt hàng, v.v.) để làm việc ngoài số lượng.


Cảm ơn các câu trả lời chi tiết. Điều này có thể nhiều hơn một chút so với tôi cần, nhưng vẫn thú vị để học hỏi từ đó (thực sự tôi có thể sử dụng điều này ở một nơi khác trong ứng dụng). Suy nghĩ của bạn về hiệu suất đối với các phương pháp tiếp cận khác nhau được đề cập trên trang này là gì?
Andy Harvey

2

Trên Rails 6, tôi có điều này để hoạt động hoàn hảo:

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

Tôi không thể nhận được bất kỳ câu trả lời nào khác để làm việc.


Tương tự trên đường ray 5.2+
Glenn
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.