我正在建立一个发票应用程序,其中发票有很多物品和付款。
在我的索引视图中,我显示了所有发票的列表,包括两个虚拟属性:
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相对较新。所以我想知道是否有最佳实践可以效仿?
答案 0 :(得分:1)
你可以在这里使用的最简单的改进可能是急切的加载。
当你加载Invoice
个对象时,如果你急于加载相关的项目和付款,你会做3个查询而不是很多。
所以,如果你有一个控制器动作做这样的事情:
def index
@invoices = Invoice.all
end
您可以将其更改为:
@invoices = Invoice.includes(:payments, :items).all
此更改应该可以加快速度,并且不需要您更改total
或balance
方法 - 它们仍然会执行相同的操作,但会获取所需的所有对象一次,而不是一次几次。
现在,这仍然(可能至少)将大量对象加载到内存中。如果您要在该视图中显示与各个项目和付款相关的各种数据,则可能无需执行任何操作。但是,如果所有视图需求都是总计和余额值,您可以让数据库为您执行此操作并跳过实例化对象,如下所示:
@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')
当您使用这样的自定义select子句时,额外列将作为具有列名称的属性移植到返回的对象上,因此您可以执行以下操作:
@invoices.first.remaining_balance
我为这些列使用了不同的名称,以便它们不会与您现有的余额和总方法重叠 - 您可能需要也可能不需要这些名称,因为这些值仅在它们被调用的对象上存在时才存在自定义选择描述。如果在没有它的情况下加载它们,尝试调用.remaining_balance
将产生NoMethodError。
警告:我正在使用PostgreSQL 9.1.3,如果你不是,上面的内容可能需要稍加修改。此外,适配器很容易将这些值作为字符串返回,因此您可能需要调用.to_f
或类似的东西。