def complete(invoice)
invoice.settle!
@notifier.deliver(invoice)
end deliver, and two of them received it at this call site. A signature
change has to update those two.
The problem
With duck typing, the @notifier.deliver(invoice) call in this method could go to any class that
defines deliver:
def complete(order)
invoice = order.invoice || Invoice.new(account: order.account, number: order.number, issued_on: Date.current)
card = Card.for(order.account)
receipt = @gateway.charge(order.total)
Finance::Accounts.new.debit(order.account, order.total, memo: "#{order.number} #{card.masked}")
invoice.save! if invoice.new_record?
Proration.for(invoice).amount
@notifier.deliver(invoice)
Curriculum.for(order.student).unlock_paid_books(order.student)
receipt
end
“Go to definition” on deliver offers five methods, in EmailNotifier, SmsNotifier,
SlackNotifier, NullNotifier and Mailer::Legacy, and grep finds the same five. You need
deliver to take a locale, so you have to know which of the five this call reaches. Changing all
five is wasted work if three are dead, and changing the wrong ones is a production bug.
A static tool has no way to narrow the five down, because @notifier is whatever someone passed to
Checkout.new, and that is decided in a controller, a job or a spec in another file.
The fix
Ask about the call site:
$ ra who_actually_answered app/billing/billing/checkout.rb:15
@notifier.deliver(invoice)
· app/billing/billing/checkout.rb:15
answered by app runs spec calls
EmailNotifier#deliver 50 1
NullNotifier#deliver 8 3
never answered here Mailer::Legacy, SlackNotifier, SmsNotifier
The two columns are app runs and spec examples. Two classes answer this call: EmailNotifier in the
50 checkouts the traffic run made and in one spec, and NullNotifier in the renewal job and in three
spec calls where notifications are not under test. Those two need the new keyword:
class EmailNotifier
def deliver(invoice, locale: I18n.default_locale)
InvoiceMailer.with(invoice: invoice, locale: locale).receipt.deliver_now
end
end
class NullNotifier
def deliver(_invoice, locale: nil)
nil
end
end
The other three can wait until something that calls them needs a locale. dead_api reports whether
anything calls them.
Where it shows up
- In your editor (LSP), “Go to implementation” on a line lists the methods that answered the calls made from that line in the recording, instead of every method with that name.
- Over MCP, your agent can ask which implementations a call site reaches before it changes a method, and edit those.
- On the command line, run
ra who_actually_answeredon afile:line, or on a method to list its call sites and ask again with one of them.
How it works
Every recorded call is an edge: the call site in the caller (file and line), the method that ran, and
the class of the object it ran on. who_actually_answered groups those edges by call site and counts
the receiving classes.
Each call tree is stamped with its entry point, so the counts are split into app runs and spec
examples. NullNotifier answering in specs alone is a different fact from NullNotifier answering
in a job, and the two columns show which one you have. The never answered here line lists every
class in the source that defines the message and did not answer.
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 who_actually_answered app/billing/billing/checkout.rb:15
The notifiers are in app/services/. The controller passes an EmailNotifier, and the renewal job
and the checkout specs pass a NullNotifier.
Reading the result
- Treat
never answered hereas unconfirmed until you have recorded your jobs and tasks as well, because a path you did not record can still send the message to that class.
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 →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 →what_a_change_touches
What a signature change affects. For each caller of a method, the argument classes it passed and the messages it sent to the return value, so you can see which callers a signature change affects before you make it.
See more →