mirror of
https://github.com/we-promise/sure.git
synced 2026-05-30 07:49:01 +00:00
perf(savings_goals/show): preload + memoize cut show from 17 to 5 queries
- set_savings_goal: with_current_balance + includes(savings_contributions: :account, linked_accounts: []) so contributions / accounts / current balance don't re-query inside helpers and view partials - SavingsGoal#status + #average_monthly_contribution: defined?(@ivar) memoization so the 5+ callsites per show (header banner, projection_summary, donut, goal-card pace, stats_for) don't recompute the exists?/MIN/SUM triplet each time - SavingsGoal#projection_payload: sort loaded contributions in Ruby instead of running a fresh ORDER BY - SavingsGoalsController#show: replace .chronological re-query with in-memory sort over the preloaded association - funding_breakdown_for: group_by + transform_values off the loaded collection instead of an extra GROUP BY SQL - stats_for: contributions_count uses .size to read the loaded cache instead of issuing COUNT(*)
This commit is contained in:
@@ -24,7 +24,9 @@ class SavingsGoalsController < ApplicationController
|
||||
end
|
||||
|
||||
def show
|
||||
@contributions = @savings_goal.savings_contributions.includes(:account).chronological
|
||||
@contributions = @savings_goal.savings_contributions
|
||||
.sort_by { |c| [ c.contributed_at, c.created_at ] }
|
||||
.reverse
|
||||
@funding_breakdown = funding_breakdown_for(@savings_goal)
|
||||
@stats = stats_for(@savings_goal)
|
||||
@breadcrumbs = [
|
||||
@@ -114,7 +116,10 @@ class SavingsGoalsController < ApplicationController
|
||||
|
||||
private
|
||||
def set_savings_goal
|
||||
@savings_goal = Current.family.savings_goals.find(params[:id])
|
||||
@savings_goal = Current.family.savings_goals
|
||||
.with_current_balance
|
||||
.includes(savings_contributions: :account, linked_accounts: [])
|
||||
.find(params[:id])
|
||||
end
|
||||
|
||||
def savings_goal_params
|
||||
@@ -156,8 +161,8 @@ class SavingsGoalsController < ApplicationController
|
||||
|
||||
def funding_breakdown_for(goal)
|
||||
totals = goal.savings_contributions
|
||||
.group(:account_id)
|
||||
.sum(:amount)
|
||||
.group_by(&:account_id)
|
||||
.transform_values { |arr| arr.sum(&:amount) }
|
||||
goal.linked_accounts.map do |account|
|
||||
amount = totals[account.id] || 0
|
||||
{ account: account, amount: amount, money: Money.new(amount, goal.currency) }
|
||||
@@ -219,7 +224,7 @@ class SavingsGoalsController < ApplicationController
|
||||
{
|
||||
avg_monthly: avg,
|
||||
avg_monthly_sub: sub_avg,
|
||||
contributions_count: goal.savings_contributions.count,
|
||||
contributions_count: goal.savings_contributions.size,
|
||||
monthly_target_sub: sub_target,
|
||||
projection_summary: summary
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ class SavingsGoal < ApplicationRecord
|
||||
# date ascending. Consumed by the
|
||||
# `savings-goal-projection-chart` Stimulus controller.
|
||||
def projection_payload
|
||||
sorted = savings_contributions.order(contributed_at: :asc).to_a
|
||||
sorted = savings_contributions.sort_by(&:contributed_at)
|
||||
running = 0
|
||||
saved_series = sorted.map do |c|
|
||||
running += c.amount.to_d
|
||||
@@ -167,26 +167,38 @@ class SavingsGoal < ApplicationRecord
|
||||
# :behind → has target_date and current pace < required monthly pace
|
||||
# :no_target_date → progress < 100 and target_date is nil
|
||||
def status
|
||||
return :reached if progress_percent >= 100
|
||||
return :no_target_date if target_date.nil?
|
||||
return :on_track if monthly_target_amount.to_d <= average_monthly_contribution.to_d
|
||||
return @status if defined?(@status)
|
||||
|
||||
:behind
|
||||
@status = if progress_percent >= 100
|
||||
:reached
|
||||
elsif target_date.nil?
|
||||
:no_target_date
|
||||
elsif monthly_target_amount.to_d <= average_monthly_contribution.to_d
|
||||
:on_track
|
||||
else
|
||||
:behind
|
||||
end
|
||||
end
|
||||
|
||||
def average_monthly_contribution
|
||||
return 0 if savings_contributions.empty?
|
||||
return @average_monthly_contribution if defined?(@average_monthly_contribution)
|
||||
|
||||
first_at = if savings_contributions.loaded?
|
||||
savings_contributions.map(&:contributed_at).compact.min
|
||||
@average_monthly_contribution = if savings_contributions.empty?
|
||||
0
|
||||
else
|
||||
savings_contributions.minimum(:contributed_at)
|
||||
first_at = if savings_contributions.loaded?
|
||||
savings_contributions.map(&:contributed_at).compact.min
|
||||
else
|
||||
savings_contributions.minimum(:contributed_at)
|
||||
end
|
||||
if first_at.blank?
|
||||
current_balance
|
||||
else
|
||||
months = ((Date.current.year - first_at.year) * 12 + (Date.current.month - first_at.month)) + 1
|
||||
months = 1 if months < 1
|
||||
(current_balance.to_d / months).round(2)
|
||||
end
|
||||
end
|
||||
return current_balance if first_at.blank?
|
||||
|
||||
months = ((Date.current.year - first_at.year) * 12 + (Date.current.month - first_at.month)) + 1
|
||||
months = 1 if months < 1
|
||||
(current_balance.to_d / months).round(2)
|
||||
end
|
||||
|
||||
def last_contribution_at
|
||||
|
||||
Reference in New Issue
Block a user