From 444f5e6a2da48372b75af7dd2943efd4e66d23da Mon Sep 17 00:00:00 2001 From: William Wei Ming <280573057+bittensorrider@users.noreply.github.com> Date: Thu, 21 May 2026 02:28:26 +0800 Subject: [PATCH] optimize net_category_totals() by using memoized cache (#1881) * optimize net_category_totals() by using memoized cache * fix issue - net_category_totals cache is never populated - suggested by coderabbitAI --- app/models/income_statement.rb | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/app/models/income_statement.rb b/app/models/income_statement.rb index f885e3f81..6082f8e74 100644 --- a/app/models/income_statement.rb +++ b/app/models/income_statement.rb @@ -30,14 +30,23 @@ class IncomeStatement end def expense_totals(period: Period.current_month) - build_period_total(classification: "expense", period: period) + # Memoized per instance so callers that also invoke `net_category_totals` + @expense_totals_by_period ||= {} + @expense_totals_by_period[period_cache_key(period)] ||= + build_period_total(classification: "expense", period: period) end def income_totals(period: Period.current_month) - build_period_total(classification: "income", period: period) + @income_totals_by_period ||= {} + @income_totals_by_period[period_cache_key(period)] ||= + build_period_total(classification: "income", period: period) end def net_category_totals(period: Period.current_month) + @net_category_totals_by_period ||= {} + cached = @net_category_totals_by_period[period_cache_key(period)] + return cached if cached + expense = expense_totals(period: period) income = income_totals(period: period) @@ -87,7 +96,7 @@ class IncomeStatement CategoryTotal.new(category: r[:category], total: r[:total], currency: family.currency, weight: weight) end - NetCategoryTotals.new( + @net_category_totals_by_period[period_cache_key(period)] = NetCategoryTotals.new( net_expense_categories: net_expense_categories, net_income_categories: net_income_categories, total_net_expense: total_net_expense, @@ -126,6 +135,10 @@ class IncomeStatement @categories ||= family.categories.all.to_a end + def period_cache_key(period) + [ period.start_date, period.end_date ] + end + def build_period_total(classification:, period:) # Exclude pending transactions from budget calculations totals = totals_query(transactions_scope: family.transactions.visible.excluding_pending.in_period(period), date_range: period.date_range).select { |t| t.classification == classification }