* perf: memoize Family#balance_sheet/investment_statement, cache transactions-index side queries
The account sidebar renders on every page (mobile + desktop, 3 tabs each)
and calls Family#balance_sheet multiple times per render; neither it nor
Family#investment_statement/InvestmentStatement#current_holdings were
memoized, so each call rebuilt the underlying query from scratch. Memoize
both per-user (family sharing means different users must not share a
cached BalanceSheet/InvestmentStatement).
Also cache TransactionsController#index's uncategorized_count and
projected_recurring lookups, which run unconditionally on every request
regardless of whether the underlying data changed, using the same
entries_cache_version-keyed pattern already used elsewhere in the codebase
(e.g. Transaction::Search#totals).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix: address PR #3058 review feedback on cache invalidation and query reuse
- Invalidate transactions-index caches when the current user's AccountShare
access changes, not just on entries/recurring updates (CodeRabbit/Codex
flagged revoked users could see stale data for up to a day).
- Use full-precision timestamps instead of to_i in the cache keys so
same-second updates aren't missed.
- Reuse the already-memoized investment_account_ids in
InvestmentStatement#current_holdings instead of an extra any? query.
- Assert the rendered response instead of a controller instance variable in
the uncategorized_count test, per CodeRabbit nitpick.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix: assert rendered response, not implementation details, in PR #3058 tests
Two CodeRabbit nitpicks from the second review round: the uncategorized-count
cache-reuse assertion matched a scope name that never appears in generated SQL
(making it vacuous), and the recurring-cache revocation test read the
controller's private @projected_recurring ivar instead of the rendered page.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix: bust transactions-index caches on entry/recurring deletion and account status change
jjmata's PR review flagged two invalidation gaps: hard-deleting an
uncategorized entry or recurring transaction left the previous max
updated_at unchanged (cache never busted), and toggling an account's
active status doesn't touch entries/AccountShare at all. Fold in
counts (like account_share_version already did) and a new
Family#accounts_status_version, and move the version helpers onto
Family/Current per the "fat models, skinny controllers" nit.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix: include merchant version in projected_recurring cache key
Editing or deleting a FamilyMerchant doesn't touch recurring_transactions,
so the cached projected-recurring list (rendered with merchant name/logo,
expires_in: 1.day) could show stale merchant data for up to a day.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(transactions): version projected-recurring cache by referenced merchants, not just FamilyMerchant
recurring_transactions.merchant_id can point at a shared ProviderMerchant
(recurring detection copies transaction.merchant_id), not just a
family-owned FamilyMerchant. merchants_version only tracked
Family#merchants (FamilyMerchant), so a ProviderMerchant update (e.g.
ProviderMerchant::Enhancer setting name/logo) left the cached projected
recurring list stale for up to a day.
Replace Family#merchants_version with #recurring_transaction_merchants_version,
scoped to the merchant records actually referenced by the family's recurring
transactions (both FamilyMerchant and ProviderMerchant), and bump the cache
key version. Also fix a flaky test assertion that matched all
recurring_transactions-table queries instead of the actual projection query,
since computing the cache key itself still runs small COUNT/MAX queries
against that table on a cache hit.
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Gerald <248542187+gfr-free@users.noreply.github.com>