Skip to content
Open
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6542ad5
fix(ci): skip scheduled preview cleanup on forks
bittensorrider Jul 4, 2026
d1133f7
Merge branch 'we-promise:main' into main
bittensorrider Jul 9, 2026
261b8dd
Merge branch 'we-promise:main' into main
bittensorrider Jul 14, 2026
20f71e2
Merge branch 'we-promise:main' into main
bittensorrider Jul 15, 2026
b2f105d
Merge branch 'we-promise:main' into main
bittensorrider Jul 17, 2026
399fadc
Merge branch 'we-promise:main' into main
bittensorrider Jul 18, 2026
9e8d0fe
Merge branch 'we-promise:main' into main
bittensorrider Jul 19, 2026
42818d7
Merge branch 'we-promise:main' into main
bittensorrider Jul 21, 2026
3b72ecc
Merge branch 'we-promise:main' into main
bittensorrider Jul 22, 2026
503edef
Merge branch 'we-promise:main' into main
bittensorrider Jul 23, 2026
e597c91
Merge branch 'we-promise:main' into main
bittensorrider Jul 23, 2026
ff344ae
Merge branch 'we-promise:main' into main
bittensorrider Jul 25, 2026
1ba043f
Merge branch 'we-promise:main' into main
bittensorrider Jul 25, 2026
33e14ce
Merge branch 'we-promise:main' into main
bittensorrider Jul 25, 2026
7db025d
Merge branch 'we-promise:main' into main
bittensorrider Jul 25, 2026
9d2032b
Merge branch 'we-promise:main' into main
bittensorrider Jul 26, 2026
a26e165
Merge branch 'we-promise:main' into main
bittensorrider Jul 26, 2026
ba6b24c
Merge remote-tracking branch 'upstream/main'
bittensorrider Jul 26, 2026
5beea3a
Merge branch 'we-promise:main' into main
bittensorrider Jul 26, 2026
ef124be
Merge branch 'main' of https://github.com/bittensorrider/sure
bittensorrider Jul 26, 2026
aa8f2bd
Preload transfer counterparty associations on transactions index
bittensorrider Jul 26, 2026
68fb97e
Assert transfer rows render in transactions index N+1 test
bittensorrider Jul 26, 2026
5be95ce
Broaden transactions index N+1 SQL matchers for lazy loads
bittensorrider Jul 26, 2026
db72596
Drop unused outflow transfer preloads on transactions index
bittensorrider Jul 26, 2026
89df259
Treat only equality SQL lookups as N+1 in index test
bittensorrider Jul 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion app/controllers/transactions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,20 @@ def index
.includes(
{ entry: :account },
:category, :merchant, :tags,
:transfer_as_inflow, :transfer_as_outflow
# Transfer#categorizable? / #payment? walk
# transfer.inflow_transaction.entry.account (to_account).
# Kind checks in the list use the current Transaction, so
# outflow_transaction need not be preloaded here.
{
transfer_as_inflow: {
inflow_transaction: { entry: :account }
}
},
{
transfer_as_outflow: {
inflow_transaction: { entry: :account }
}
}
)

@pagy, @transactions = pagy(base_scope, limit: safe_per_page)
Expand Down
67 changes: 67 additions & 0 deletions test/controllers/transactions_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -722,11 +722,78 @@ class TransactionsControllerTest < ActionDispatch::IntegrationTest
assert_nil created_entry.transaction.extra["exchange_rate"]
end

test "index preloads transfer counterparty entry and account to avoid N+1" do
family = @user.family
from_account = family.accounts.visible.first
to_account = family.accounts.create!(
name: "Transfer Counterparty",
currency: family.currency,
balance: 0,
accountable: Depository.new
)

transfers = 6.times.map do |i|
create_transfer(
from_account: from_account,
to_account: to_account,
amount: 25 + i,
date: Date.current - i.days
)
end

queries = capture_sql_queries do
# per_page must fit all transfer legs + fixtures so assertions below
# actually exercise the transfer render path this preload protects.
get transactions_url(per_page: 50)
end

assert_response :success

# Index dedupes transfers to the outflow side; assert those rows rendered so
# the SQL assertions below actually exercise Transfer#to_account.
rendered_ids = rendered_entry_ids
transfers.each do |transfer|
assert_includes rendered_ids, transfer.outflow_transaction.entry.id.to_s,
"Expected transfer outflow entry to render on the index"
end

# Transfer#categorizable? / #payment? walk to_account via
# transfer.inflow_transaction.entry.account. Without nested includes those
# become one lookup triad per transfer row during list render.
normalized_queries = queries.map { |sql| normalize_sql_query(sql) }
assert_empty single_record_lookups(normalized_queries, table: "transactions", column: "id"),
"Expected transfer counterparty transactions to be preloaded"
assert_empty single_record_lookups(normalized_queries, table: "entries", column: "entryable_id"),
"Expected transfer counterparty entries to be preloaded"
assert_empty single_record_lookups(normalized_queries, table: "accounts", column: "id"),
"Expected transfer counterparty accounts to be preloaded"
end

private
def rendered_entry_ids
css_select("turbo-frame[id^='entry_']").map { |node| node["id"].delete_prefix("entry_") }
end

def normalize_sql_query(sql)
sql.to_s.squish.gsub(/[`"]/, "").downcase
end

# Per-row lazy loads use `column = ?` or single-value `column IN (?)`.
# Batch preloads use multi-value `IN (?, ?, ...)` and must not match.
def single_record_lookups(normalized_queries, table:, column:)
pattern = /
from\s+#{Regexp.escape(table)}\s+
where\s+#{Regexp.escape(table)}\.#{Regexp.escape(column)}\s*
(?:
=
|
in\s*\(\s*[^,)]+\s*\)
)
/x

normalized_queries.grep(pattern)
end

def capture_sql_queries
queries = []
callback = lambda do |_name, _started, _finished, _unique_id, payload|
Expand Down
Loading