def debit(amount, memo:)
Entry.create!(…)
end The problem
Finance::Accounts#debit takes an account and an amount and returns the ledger entry it made. You
want to stop returning the entry, because most callers ignore it, and you suspect one caller passes
the amount in pennies rather than as Money:
def debit(account, amount, memo:)
raise InsufficientFunds, "#{account.balance} < #{amount}" if account.balance < amount
account.balance = account.balance - amount
account.save!
entry = @ledger.record(account, Money.zero - amount, memo: memo)
@audit.log(Reporting::Formatters::Currency.new(amount).to_ledger_s)
entry
end
To know what each caller passes, and which callers use the return value and how, you have to read every one of them and follow each value by hand. The method has no declared types, so a type checker has nothing to check.
The fix
Ask before you change it:
$ ra what_a_change_touches Finance::Accounts#debit
Finance::Accounts#debit
· 91 calls from 7 callers
argument 1 · account
Account Billing::Checkout#complete app/billing/billing/checkout.rb:12 63
Account FeeSchedule#charge app/services/fee_schedule.rb:6 12
Account Refund#process app/services/refund.rb:9 7
Account Transfer#call app/services/transfer.rb:9 5
Account spec/finance/accounts_spec.rb spec/finance/accounts_spec.rb:7 1
Account spec/finance/accounts_spec.rb spec/finance/accounts_spec.rb:12 1
Account Invoice#settle app/models/invoice.rb:53 1
Account bin/traffic bin/traffic:44 1
argument 2 · amount
Money Billing::Checkout#complete app/billing/billing/checkout.rb:12 63
Money FeeSchedule#charge app/services/fee_schedule.rb:6 12
Money Refund#process app/services/refund.rb:9 7
Money Transfer#call app/services/transfer.rb:9 5
Money spec/finance/accounts_spec.rb spec/finance/accounts_spec.rb:7 1
Money spec/finance/accounts_spec.rb spec/finance/accounts_spec.rb:12 1
Money Invoice#settle app/models/invoice.rb:53 1
Money bin/traffic bin/traffic:44 1
return value · Finance::Entry
receives #reference Refund#process
unused Billing::Checkout#complete, spec/finance/accounts_spec.rb, FeeSchedule#charge, Invoice#settle, Transfer#call
Every caller passes Money, so the suspicion about pennies was wrong. The return change affects one
caller, Refund#process, which reads #reference from the entry to decide whether the invoice is
refunded:
entry = ledger.debit(@invoice.account, @invoice.total, memo: "refund #{@invoice.number}")
@invoice.update!(state: "refunded") if entry.reference
The other callers ignore the return value, so the change does not affect them. Give Refund a
method whose name says it returns the entry, and let debit return nothing:
# app/services/refund.rb:9
entry = ledger.record_debit(@invoice.account, @invoice.total, memo: "refund #{@invoice.number}")
Where it shows up
- Over MCP, your agent can ask before editing a signature and get the per-argument table and the return usage as structured blocks.
- On the command line, run
ra what_a_change_touches Klass#method.
How it works
- The callers are every recorded call edge into
debit, with the method and line each one came from. A call from a spec body or a script has no calling method, so its file is listed as the caller. - Each call records the class of every argument it passed, which is how each caller’s
amountis known to be aMoney. The parameter names come from the method’s source. - Each call records the identity of the object it returned. Within the same call tree, any later
call whose receiver is that same object is a message sent to the return value. Grouping those
messages by caller shows how each caller uses what
debitreturns:Refundsends#referenceand the others send nothing.
Run it on your app
The output on this page comes from our example Rails app, and it is regenerated whenever a tool
changes. These are the commands behind it. On your own app, run ra record first, then put your
own class, method or file in their place.
ra what_a_change_touches 'Finance::Accounts#debit'
The method is app/finance/finance/accounts.rb:14 and the one caller that reads the entry is
app/services/refund.rb:9-10.
Reading the result
- The list covers what ran. A caller that did not run in any recorded run is missing from it, along
with the argument class it would have passed. For a method with as many callers as
debit, search the source as well. - Use of a return value is read from the messages sent to it. A return value that is passed along untouched, or stored and read later in a different run, receives no messages in the tree that returned it, so the report shows it as unused.
Every tool reads the same recording. What gets recorded and how far to trust an answer are on the questions page.
Related tools
what_calls
The callers of a method. Every caller of a method in the recorded runs, with the file and line of each call and how often it was made, including calls made through public_send. what_it_calls lists the methods it calls.
See more →observed_shape
The interface a call depends on. The messages your code sends to a value, read from recorded runs. They are the interface a call depends on, whether or not anyone declared it.
See more →what_it_receives
What a method receives. For each argument of a method, the classes it was given in recorded calls and the messages the method sent to it.
See more →