0

請求書に多くのアイテムと支払いがある請求書発行アプリケーションを構築しています。

私のインデックス ビューでは、2 つの仮想属性を含むすべての請求書のリストを表示しています。

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

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

インデックス ビューを表示するには、非常に多くの SQL が必要であることに気付きました。代わりに、さらに 2 つのテーブル列を作成することをお勧めしますか? これまでのところ、冗長なデータが多すぎるのが好きではないので、そうしないことにしました。

これは私のコントローラーです:

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>

インデックス ビューの請求書ごとに、次の 4 つの 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

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_balanceNoMethodError が生成されます。

警告: 私は PostgreSQL 9.1.3 を使用しています。そうでない場合は、上記を少し変更する必要があるかもしれません。また、アダプターはこれらの値を文字列として返すという奇妙な傾向があるため、それらに対して を呼び出す必要がある場合があり.to_fます。

于 2012-09-27T20:22:56.733 に答える