【问题标题】:How to display unique records from a has_many through relationship?如何通过关系显示 has_many 中的唯一记录?
【发布时间】:2011-08-16 08:11:02
【问题描述】:

我想知道通过 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 之间区别的更多信息!和不同()?

通过has_many,通过关系生成唯一记录列表的最佳方法是什么?

谢谢

【问题讨论】:

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


    【解决方案1】:

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

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

    来自the Rails documentation

    :uniq

    如果为 true,将从集合中省略重复项。与 :through 结合使用很有用。

    RAILS 4 更新:

    在 Rails 4 中,has_many :products, :through =&gt; :orders, :uniq =&gt; true 已弃用。相反,你现在应该写has_many :products, -&gt; { distinct }, through: :orders。请参阅distinct section for has_many: :through relationships on the ActiveRecord Associations documentation 了解更多信息。感谢 Kurt Mueller 在评论中指出这一点。

    【讨论】:

    • 区别不在于您是否删除模型或控制器中的重复项,而是您在关联中使用 :uniq 选项(如我的回答所示)或 SQL DISTINCT stmt(例如 has_many : products, :through => :orders, :select => "DISTINCT products.*)。在第一种情况下,获取所有记录,rails 为您删除重复项。在后一种情况下,仅从非重复记录中获取如果你有一个大的结果集,它可能会提供更好的性能。
    • 在 Rails 4 中,has_many :products, :through =&gt; :orders, :uniq =&gt; true 已弃用。相反,你现在应该写has_many :products, -&gt; { uniq }, through: :orders
    • 注意 -> {uniq} 在这个意义上只是一个别名 -> {distinct} apidock.com/rails/v4.1.8/ActiveRecord/QueryMethods/uniq 它出现在 SQL 而不是 ruby​​ 中
    • 如果您与DISTINCTORDER BY 子句发生冲突,您始终可以使用has_many :products, -&gt; { unscope(:order).distinct }, through: :orders
    • 感谢@fagiani,如果您的模型有一个 json 列并且您使用 psql 它会变得更加复杂,您必须执行类似has_many :subscribed_locations, -&gt; { 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
    【解决方案2】:

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

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

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

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

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

    【讨论】:

    • 在 Rails 5.1 中,我不断收到undefined method 'except' for #&lt;Array...,这是因为我使用了.uniq 而不是.distinct
    【解决方案3】:

    在 Rails 6 上,我让它完美运行:

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

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

    【讨论】:

    • Rails 5.2+ 上相同
    【解决方案4】:

    您可以使用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
    

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

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

    【讨论】:

    • 感谢您的详细回答。这可能比我需要的多一点,但仍然值得学习(实际上我可能会在应用程序的其他地方使用它)。您对本页提到的各种方法的性能有何看法?
    【解决方案5】:

    在 rails6 中使用 -&gt; { 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>]
    

    【讨论】:

      猜你喜欢
      • 2016-02-22
      • 2016-07-07
      • 1970-01-01
      • 1970-01-01
      • 2022-01-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多