mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-18 20:59:39 +02:00
Temporary transactions page performance fix (#2372)
* Temporary transactions page performance fix * Fix Cursor bugs * More bugbot bug fixes
This commit is contained in:
parent
a5f1677f60
commit
6d9bb7f0eb
1 changed files with 82 additions and 24 deletions
|
@ -3,6 +3,8 @@ class TransactionsController < ApplicationController
|
|||
|
||||
before_action :store_params!, only: :index
|
||||
|
||||
require "digest/md5"
|
||||
|
||||
def new
|
||||
super
|
||||
@income_categories = Current.family.categories.incomes.alphabetically
|
||||
|
@ -15,35 +17,91 @@ class TransactionsController < ApplicationController
|
|||
|
||||
set_focused_record(transactions_query, params[:focused_record_id], default_per_page: 50)
|
||||
|
||||
@pagy, @transactions = pagy(
|
||||
transactions_query.includes(
|
||||
{ entry: :account },
|
||||
:category, :merchant, :tags,
|
||||
transfer_as_outflow: { inflow_transaction: { entry: :account } },
|
||||
transfer_as_inflow: { outflow_transaction: { entry: :account } }
|
||||
).reverse_chronological,
|
||||
limit: params[:per_page].presence || default_params[:per_page],
|
||||
params: ->(params) { params.except(:focused_record_id) }
|
||||
# ------------------------------------------------------------------
|
||||
# Cache the expensive includes & pagination block so the DB work only
|
||||
# runs when either the query params change *or* any entry has been
|
||||
# updated for the current family.
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
latest_update_ts = Current.family.entries.maximum(:updated_at)&.utc&.to_i || 0
|
||||
|
||||
items_per_page = (params[:per_page].presence || default_params[:per_page]).to_i
|
||||
items_per_page = 1 if items_per_page <= 0
|
||||
|
||||
current_page = (params[:page].presence || default_params[:page]).to_i
|
||||
current_page = 1 if current_page <= 0
|
||||
|
||||
# Build a compact cache digest: sanitized filters + page info + a
|
||||
# token that changes on updates *or* deletions.
|
||||
entries_changed_token = [ latest_update_ts, Current.family.entries.count ].join(":")
|
||||
|
||||
digest_source = {
|
||||
q: @q, # processed & sanitised search params
|
||||
page: current_page, # requested page number
|
||||
per: items_per_page, # page size
|
||||
tok: entries_changed_token
|
||||
}.to_json
|
||||
|
||||
cache_key = Current.family.build_cache_key(
|
||||
"transactions_idx_#{Digest::MD5.hexdigest(digest_source)}"
|
||||
)
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Cache totals
|
||||
# -------------------------------------------------------------------
|
||||
# Totals calculation is expensive (heavy SQL with grouping). We cache the
|
||||
# result keyed by:
|
||||
# • Family id
|
||||
# • The family-level cache key that already embeds entries.maximum(:updated_at)
|
||||
# • A digest of the current search params so each distinct filter set gets
|
||||
# its own cache entry.
|
||||
# When any entry is created/updated/deleted, the family cache key changes,
|
||||
# automatically invalidating all related totals.
|
||||
cache_data = Rails.cache.fetch(cache_key, expires_in: 30.minutes) do
|
||||
current_page_i = current_page
|
||||
|
||||
params_digest = Digest::MD5.hexdigest(@q.to_json)
|
||||
cache_key = Current.family.build_cache_key("transactions_totals_#{params_digest}")
|
||||
# Initial query
|
||||
offset = (current_page_i - 1) * items_per_page
|
||||
ids = transactions_query
|
||||
.reverse_chronological
|
||||
.limit(items_per_page)
|
||||
.offset(offset)
|
||||
.pluck(:id)
|
||||
|
||||
@totals = Rails.cache.fetch(cache_key) do
|
||||
Current.family.income_statement.totals(transactions_scope: transactions_query)
|
||||
total_count = transactions_query.count
|
||||
|
||||
if ids.empty? && total_count.positive? && current_page_i > 1
|
||||
current_page_i = (total_count.to_f / items_per_page).ceil
|
||||
offset = (current_page_i - 1) * items_per_page
|
||||
|
||||
ids = transactions_query
|
||||
.reverse_chronological
|
||||
.limit(items_per_page)
|
||||
.offset(offset)
|
||||
.pluck(:id)
|
||||
end
|
||||
|
||||
{ ids: ids, total_count: total_count, current_page: current_page_i }
|
||||
end
|
||||
|
||||
ids = cache_data[:ids]
|
||||
total_count = cache_data[:total_count]
|
||||
current_page = cache_data[:current_page]
|
||||
|
||||
# Build Pagy object (this part is cheap – done *after* potential
|
||||
# page fallback so the pagination UI reflects the adjusted page
|
||||
# number).
|
||||
@pagy = Pagy.new(
|
||||
count: total_count,
|
||||
page: current_page,
|
||||
items: items_per_page,
|
||||
params: ->(p) { p.except(:focused_record_id) }
|
||||
)
|
||||
|
||||
# Fetch the transactions in the cached order
|
||||
@transactions = Current.family.transactions
|
||||
.active
|
||||
.where(id: ids)
|
||||
.includes(
|
||||
{ entry: :account },
|
||||
:category, :merchant, :tags,
|
||||
transfer_as_outflow: { inflow_transaction: { entry: :account } },
|
||||
transfer_as_inflow: { outflow_transaction: { entry: :account } }
|
||||
)
|
||||
|
||||
# Preserve the order defined by `ids`
|
||||
@transactions = ids.map { |id| @transactions.detect { |t| t.id == id } }.compact
|
||||
|
||||
@totals = Current.family.income_statement.totals(transactions_scope: transactions_query)
|
||||
end
|
||||
|
||||
def clear_filter
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue