0

我正在构建一个发票应用程序,其中发票有许多项目和付款。

在我的索引视图中,我显示了所有发票的列表,其中包括两个虚拟属性:

def total
  items.sum { |item| item.total }
end

def balance
  self.payments.sum(:amount) - self.total
end

我注意到显示索引视图需要大量的 SQL。是否建议再创建两个表列?到目前为止,我选择不这样做,因为我不喜欢有太多冗余数据。

这是我的控制器

def index
  result = current_user.invoices.includes(:items, :payments)
  @invoices = paginate(result)
end

index.html.erb

<table id="index">
  <thead>
    <tr>
      <th>Number</th>
      <th>Date</th>
      <th>Total</th>
      <th>Balance</th>
      <th></th>
    </tr>
  </thead>
  <tbody>
    <%= render @invoices %>
  </tbody>
</table>
<%= will_paginate @invoices %>

_invoice.html.erb

<tr>
<td>
    <%= link_to invoice.number, invoice_path(invoice) %>
</td>
<td>
    <%= l invoice.date %>
</td>
<td>
    <%= number_to_currency(invoice.total) %>
</td>
<td>
    <%= number_to_currency(invoice.balance) %>
</td>       
<td>
    <%= destroy_link(invoice) %>
</td>
</tr>

对于索引视图上的每张发票,将生成以下四个 SQL 查询:

(0.1ms)  SELECT SUM("payments"."amount") AS sum_id FROM "payments" WHERE "payments"."invoice_id" = 19
CACHE (0.0ms)  SELECT "items".* FROM "items" WHERE "items"."invoice_id" = 19
CACHE (0.0ms)  SELECT SUM("payments"."amount") AS sum_id FROM "payments" WHERE "payments"."invoice_id" = 19
CACHE (0.0ms)  SELECT "items".* FROM "items" WHERE "items"."invoice_id" = 19

(这使得每个索引页有 40 个 SQL 查询。)

我必须承认我对 Rails 比较陌生。所以我想知道是否有最佳实践可以遵循?

4

1 回答 1

1

您可以在这里使用的最简单的改进可能是急切加载。

当您加载Invoice对象时,如果您急切地加载关联的项目和付款,您将执行 3 次查询,而不是很多。

因此,如果您有一个执行以下操作的控制器操作:

def index
  @invoices = Invoice.all
end

您可以将其更改为:

@invoices = Invoice.includes(:payments, :items).all

这种改变应该会加快速度,并且不需要你改变你的totalorbalance方法——它们仍然做同样的事情,但是一次获取他们需要的所有对象,而不是一次获取几个。

现在,这仍然(至少可能)将相当多的对象加载到内存中。如果您要在该视图中显示与单个项目和付款相关的各种数据,则可能无需执行任何操作。但是,如果所有视图需要的是总和余额值,您可以让您的数据库为您执行此操作并跳过实例化对象,如下所示:

@invoices = Invoice.select("invoices.*, sum(items.total) as item_total, (sum(payments.amount) - sum(items.total)) as remaining_balance").joins(:items, :payments).group('invoices.id')

当您使用这样的自定义选择子句时,额外的列将作为具有列名称的属性移植到返回的对象上,因此您可以执行以下操作:

@invoices.first.remaining_balance

我为这些列使用了不同的名称,以便它们不会与您现有的 balance 和 total 方法重叠 - 您可能需要也可能不需要这些,因为这些值仅在调用它们的对象使用自定义选择时存在描述。如果它们在没有它的情况下加载,尝试调用.remaining_balance将产生 NoMethodError。

注意事项:我使用的是 PostgreSQL 9.1.3,如果你不是,上面可能需要稍微修改一下。此外,适配器有一种奇怪的倾向,即会将这些值作为字符串返回,因此您可能需要.to_f对它们进行调用或类似的操作。

于 2012-09-27T20:22:56.733 回答