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

时间:2011-05-01 05:19:59

标签: ruby-on-rails unique distinct ruby-on-rails-3

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

由于

5 个答案:

答案 0 :(得分:218)

您是否尝试在has_many关联上指定:uniq选项:

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

来自the Rails documentation

  

:uniq

     

如果为true,将从集合中省略重复项。与以下内容结合使用:通过。

铁路更新4:

在Rails 4中,不推荐使用has_many :products, :through => :orders, :uniq => true。相反,您现在应该写has_many :products, -> { distinct }, through: :orders。有关详细信息,请参阅distinct section for has_many: :through relationships on the ActiveRecord Associations documentation。感谢Kurt Mueller在评论中指出这一点。

答案 1 :(得分:40)

请注意,自{Rails 4}起,uniq: true的有效选项已删除has_many

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

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

导轨指南涵盖了这种以及您可以使用范围过滤关系查询的其他方式,向下滚动到第4.3.3节:

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

答案 2 :(得分: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

通过这种方式,您可以避免在仅返回一个产品时出现的问题,因为您始终知道它将返回一个哈希,产品作为密钥和一组订单。

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

答案 3 :(得分:4)

在Rails 6上,我可以完美地运行它:

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

我无法获得其他任何答案。

答案 4 :(得分:0)

在 rails6 中使用 -> { distinct } 在范围内它会工作

class Person
  has_many :readings
  has_many :articles, -> { distinct }, through: :readings
end
 
person = Person.create(name: 'Honda')
article   = Article.create(name: 'a1')
person.articles << article
person.articles << article
person.articles.inspect # => [#<Article id: 7, name: "a1">]
Reading.all.inspect     # => [#<Reading id: 16, person_id: 7, article_id: 7>, #<Reading id: 17, person_id: 7, article_id: 7>]