runtimeanalyz.ing

← All capabilities

who_actually_answered

CLI · LSP · MCP free

The classes that answered a call. At a duck-typed call site, the concrete classes that received the message in recorded runs, and how often each did. In an editor it answers go to implementation from the recording.

Checkout checkout.rb:6
def complete(invoice)
  invoice.settle!
  @notifier.deliver(invoice)
end
deliver ( invoice Invoice )
50 app runs · 1 spec call
EmailNotifier answered
deliver ( invoice Invoice )
8 app runs · 3 spec calls
NullNotifier answered
deliver
never reached from here
SmsNotifier defines deliver
deliver
never reached from here
SlackNotifier · Mailer::Legacy define deliver
Five classes define 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_answered on a file: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 here as 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.

what_calls

CLI · LSP · MCP free

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

CLI · LSP · MCP free

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

CLI · MCP Pro

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 →