【问题标题】:rails how to have a model for a .group_by assocation in a controllerrails 如何在控制器中拥有 .group_by 关联的模型
【发布时间】:2020-06-12 20:36:00
【问题描述】:

这里是新开发者。 我有一个customer_controller.rb,它收集所有已完成的订单并通过电子邮件地址对它们进行分组以获取我们的“客户”列表。

客户控制器:

class CustomerController < ApplicationController

  def index
    orders = Spree::Order.where(completed_at: from...to)
    @customers = orders.group_by(&:email)
  end

end

我有这样的视图模板:

<% @customers.each_pair do |email, orders| %>
<% total_orders = Spree::Order.where(state: "complete", email: email) %>
<% arr = orders  %>
<% amount = arr.inject(0) {|sum, hash| sum + hash[:payment_total]} %>
<tbody id="tbodyid">
  <tr>
    <td><%= email %></td>
    <th><%= orders.count %></td>
    <td><%= total_orders.count %></td>
    <td><%= total_orders.first.completed_at.strftime("%D") if total_orders.first.completed_at.present? %></td>
    <td>$<%= amount %></td>
  </tr>
</tbody>

<% end %>

为此处理模型的最佳方法是什么?将这种逻辑放在视图中是服务器的噩梦。我想要一个基于@customers 实例的模型,但我不知道该怎么做。

我是否完全放弃这个方向并修改order_decorator.rb 模型以获取客户集合?

编辑:针对以下问题,这里有一个 Spree::Order 记录。所有订单都有电子邮件,并非所有订单都有 user_id。

#<Spree::Order id: 334, number: "R826173067", item_total: #<BigDecimal:7f9ee91516b8,'0.2198E2',18(18)>, total: #<BigDecimal:7f9ee91515f0,'0.1999E2',18(18)>, state: "complete", adjustment_total: #<BigDecimal:7f9ee91514b0,'-0.199E1',18(18)>, user_id: 45, completed_at: "2020-06-10 15:43:35", bill_address_id: 243, ship_address_id: 244, payment_total: #<BigDecimal:7f9ee9150e20,'0.1999E2',18(18)>, shipping_method_id: nil, shipment_state: "partial", payment_state: "paid", email: "bobby@boby.com", special_instructions: nil, created_at: "2020-06-10 15:42:28", updated_at: "2020-06-10 15:43:35", currency: "USD", last_ip_address: "127.0.0.1", created_by_id: 45, shipment_total: #<BigDecimal:7f9ee91500d8,'0.0',9(18)>, additional_tax_total: #<BigDecimal:7f9ee9150010,'0.0',9(18)>, promo_total: #<BigDecimal:7f9edd683ef8,'-0.199E1',18(18)>, channel: "spree", included_tax_total: #<BigDecimal:7f9edd683db8,'0.0',9(18)>, item_count: 2, approver_id: nil, approved_at: nil, confirmation_delivered: true, considered_risky: false, express: false, source: nil, purchased_via_subscription: false> 

【问题讨论】:

  • 不熟悉 Spree,你能发布一下 orders = Spree::Order.where(state: "complete") 的单条记录吗?还有你的客户模型代码是什么样的?
  • 我现在根本没有客户模型。所有的“逻辑”都在可怕的视图模板中处理。我将在上面的原始帖子中发布一条记录。谢谢
  • 你能解释一下orders.counttotal_orders.count吗?对我来说看起来一样。 amount 也只是用户完成订单的“payment_total”总和吗?
  • total_orders.first.completed_at... 也应该是 created_at 的第一个订单吗?你确定你的total_orders 数组是由 created_at 排序的吗?该字段到底应该代表什么?
  • @Beartech 很抱歉造成混淆,请参阅上面控制器中的编辑(我最初是为了简单起见进行编辑)。我从给定时间(从...到)内的订单收集电子邮件,然后有两列:一列是在时间范围内完成的订单,一列是终身订单。

标签: ruby-on-rails postgresql spree


【解决方案1】:

根据上面的答案,您可以进行查询,以满足您在控制器中的需求,而不需要太多其他内容。如果您的数据库是 Postgres,您可以执行以下操作:

@customers = Spree::Order.group(:email).select(:email, 'count(id) AS total_orders_count',
 'sum(payment_total) AS amount', 'array_agg(completed_at) AS completion_dates'
)

=> [#<Order:0x00007fbc8f0bd2d8 id: nil, email: 'foo@bar.com'>,
   #<Order:0x00007fbc8f0bd148 id: nil, email: 'jane@doe.com'>,
   #<Order:0x00007fbc8f0bcec8 id: nil, email: 'john@smith.com'>,
   #<Order:0x00007fbc8f0bcd88 id: nil, email: 'jack@jill.com'>...]

请注意,这些没有 :id 因为它们是“虚拟”对象,因为每个对象实际上都是一组与该电子邮件地址匹配的订单。但是它们都有以下方法:

total_orders => the count of completed orders for that email address
amount => sum of all payment_total for the orders belonging to that email
completion_dates => array of completed_on dates for that email's orders

您已经在数据库中完成了所有选择和计算,因此它将尽可能快,您可以像这样显示它:

<% @customers.each do |customer| %>
<tbody id="tbodyid">
  <tr>
    <td><%= customer.email %></td>   <!-- email -->
    <td><%= customer.completion_dates.count{|date| 
             date > (#some date) && (#other date)}.count %></td> <!-- orders completed between dates -->
    <td><%= customer.total_orders_count %></td> <!-- lifetime orders -->
    <td><%= customer.completion_dates.sort.last %></td>  <!-- latest completed order -->
    <td>$<%= customer.amount %></td> <!-- total payments -->
  </tr>
</tbody>
<% end %>

customer.completion_dates.sort 从最旧到最新排序。如果您想要最近的订单,请使用.last,对于最早的订单,请使用.first

您可以添加任何您想要的字段,只要您使用 count(zzz) AS xxxsum(zzz) AS xxxmax(...min(... 等函数聚合它们。或者如果您希望连接每个订单的特定字段使用该电子邮件,您必须使用array_agg(:some_column) as some_name 将其聚合到一个数组中。如果您在查询中多次使用任何聚合函数,则必须使用AS xxxx 格式,否则您将无法访问其他计数等。

@some_data = SomeClass.group(:email).select('sum(cost)', 
'count(id)')

@some_data.first.sum
#=> 23.45
@some_data.first.count
#=> 4

@some_data = SomeClass.group(:email).select('sum(cost) AS total_cost', 
'sum(tax) AS total_tax')

@some_data.first.total_cost
#=> 23.45
@some_data.first.total_tax
#=> 3.46

【讨论】:

  • 通过上面的答案,您正在处理通过一个查询获取控制器中的数据,并在视图中进行一些计数。据我所知,这不是 Rails 中的反模式。如果您真的想将其移至客户模型,您可以。
  • 这就是问题...我将如何构建客户模型?如何为非 Table 对象的 @customers 构建 group_by 关联模型?
  • 您真的需要客户模型吗?你打算用它做更多的事情吗?
  • 另外group_by 是一个可枚举的方法。如果要在查询中按 :email 分组,则必须在聚合函数中使用 .group()
  • Customer 可以是一个普通的 ruby​​ 对象,它是一个封装了围绕您要进行的查询的逻辑的类。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多