如何通过关系显示has_many中的唯一记录?


106

我想知道通过Rails3中的关系显示has_many中唯一记录的最佳方法是什么。

我有三种模式:

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通过关系生成唯一记录列表的最佳方法是什么?

谢谢

Answers:


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关联文档中has_many :: through关系不同部分。感谢Kurt Mueller在他的评论中指出了这一点。


6
区别不在于是否删除模型或控制器中的重复项,而是在关联上使用:uniq选项(如我的答案所示)或SQL DISTINCT stmt(例如has_many:products,:through =>:orders ,:select =>“ DISTINCT产品。*)。在第一种情况下,将提取所有记录,而rails会为您删除重复项。在后一种情况下,仅从数据库中获取非重复的记录,因此它可能会提供更好的性能如果您有很大的结果集

68
在Rails 4中,has_many :products, :through => :orders, :uniq => true已弃用。相反,您现在应该编写has_many :products, -> { uniq }, through: :orders
Kurt Mueller 2014年

8
请注意,-> {uniq}在某种意义上只是-> {distinct} apidock.com/rails/v4.1.8/ActiveRecord/QueryMethods/uniq的别名。它发生在SQL中而不是ruby
EngineerDave

5
如果您与DISTINCTORDER 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

请注意,uniq: true已从has_manyRails 4 的有效选项中删除了该选项。

在Rails 4中,您必须提供一个范围来配置这种行为。范围可以通过lambda提供,如下所示:

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
stevenspiel,

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

这样,您就避免了只退回一种产品时出现的问题,因为您始终知道它将返回一个以产品为键和订单数组的哈希值。

这可能对您尝试做的事有些过分,但除了数量之外,它确实为您提供了一些不错的选择(例如,订购日期等)。


感谢您的详细答案。这可能比我需要的多一点,但是仍然值得学习(实际上,我可以在应用程序的其他地方使用它)。您对本页提到的各种方法的性能有何看法?
安迪·哈维

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.