skip to content

Send & Dynamic Calls

send, public_send and __send__ call a method whose name is only known at run time, and send quietly reaches private methods. Interviewers ask which to use and why the bypass matters.

on this pageshow

explore

questions

4

In Ruby, how do you call a method whose name is only known at run time, and how do send and public_send differ?

level: juniorimportance: must knowfreq 68%

answer

  1. name as data, not source
  2. Symbol or String both accepted
  3. one ignores visibility
  4. private method '...' called
  5. default to the public variant

basics

~20 s

Pass the name as a Symbol or String to send or public_send, with any arguments after it. send calls the method even if it is private or protected; public_send calls only public methods and raises NoMethodError otherwise.

solid answer

~40 s

Ruby lets you pass the method name as data: `obj.send(:publish, arg)` or `obj.public_send("export_#{format}")`. A String name is converted to a Symbol, and extra arguments and a block go to the target method as in a normal call. The difference is visibility. `send` (and its alias `__send__`) acts like a call from inside the object, so it reaches private and protected methods. `public_send` acts like an outside call with an explicit receiver, so a private or protected target raises `NoMethodError`. I default to `public_send`, because it keeps the class's encapsulation intact, and use `send` only when calling a non-public method is deliberate, such as a framework invoking a private hook.

code

ruby · 13 lines
ruby
class Invoice
  def total = 120

  private

  def recalculate = :done
end

inv = Invoice.new
field = "total"
inv.public_send(field)        # => 120
inv.send(:recalculate)        # => :done
inv.public_send(:recalculate) # NoMethodError: private method 'recalculate' called for an instance of Invoice

go deeper

for a junior

Recall that send and public_send take the method name as a Symbol or String, and that only public_send honours private and protected.

for a middle

Explain the table: send calls from inside the object, public_send from outside, so protected is refused even between instances of the same class.

for a senior

Show judgement: public_send by default, send only for deliberate hooks, and a fixed list of allowed names when the name comes from outside the program.

for a principal

Frame dynamic calls as a code-review rule: where send appears, encapsulation is switched off, so the team should see a reason for every occurrence.

## The problem send solves In Ruby, calling a method is **sending a message** to an object: `report.publish` sends the message `:publish` to `report`. Usually the name is written in the source. Sometimes it is not known until run time: it comes from a configuration value, a column name, a command-line word or a loop over a list of names. For that case Ruby gives every ordinary object three methods that take the method name as data: - **`Kernel#send`** (`obj.send(name, *args)`) — calls the method, whatever its visibility. - **`Kernel#public_send`** (`obj.public_send(name, *args)`) — calls it only if it is **public**. - **`BasicObject#__send__`** — the same behaviour as `send` under a name that is much harder to clash with. The name may be a **Symbol** (`:publish`) or a **String** (`"publish"`); a String is converted to a Symbol before lookup. Anything else raises `TypeError` ("1 is not a symbol nor a string"), and calling `send` with no arguments at all raises `ArgumentError` ("no method name given"). ## Building the name at run time Because the name is just a value, you can assemble it: ```ruby class Report def export_csv = "id,total" def export_json = '{"id":1}' private def build_totals = :ok end report = Report.new format = "csv" report.public_send("export_#{format}") # => "id,total" report.public_send(:"export_#{format}") # same, as an interpolated Symbol report.send(:build_totals) # => :ok, private is no obstacle report.public_send(:build_totals) # NoMethodError: private method 'build_totals' called for an instance of Report ``` Operators and setters are methods too, so `1.send(:+, 2)` returns `3` and `hash.send(:[]=, :k, 1)` stores a value. Arguments after the name are passed to the target method exactly as in a normal call, and a block given to `send` is handed to the target. The return value of `send` is simply the return value of the method it called. Lookup is the normal lookup: Ruby searches the receiver's class and its ancestors, and if nothing matches it calls `method_missing`, which by default raises `NoMethodError`. Nothing about `send` creates a method or changes the class; it resolves the name it is given on every call. ## Where send and public_send part ways | | `send` / `__send__` | `public_send` | |---|---|---| | public method | called | called | | protected method | called | `NoMethodError` ("protected method … called") | | private method | called | `NoMethodError` ("private method … called") | | missing method | `method_missing`, then `NoMethodError` | same | `send` behaves like a call made **from inside the object**, where private methods are legal; `public_send` behaves like a call from **outside** with an explicit receiver. That single difference decides which to use: 1. Use **`public_send`** by default, whenever the call stands in for an ordinary `obj.name` call. It keeps the class's visibility contract: a private helper stays private even when the name arrives as data. 2. Use **`send`** only when reaching a non-public method is the point and you own that decision, for example a framework hook that calls a private callback on purpose. Every such call is a place where encapsulation is switched off, so reviewers look for it. 3. In tests, `obj.send(:private_helper)` works, but it couples the test to an implementation detail; testing through the public interface is usually the better fix. ## Common misconceptions - **"send respects private."** It does not; only `public_send` does. - **"public_send only blocks private methods."** It blocks protected ones as well, even when the caller is an instance of the same class. - **"The name must be a Symbol."** A String works and is converted. - **"method(:x) calls x."** `Object#method` returns a `Method` object; nothing runs until you call it. - **"send is a Rails helper."** Both methods are core Ruby, defined in `Kernel`. ## What an interviewer is listening for A junior answer names `send` and shows a name built from a string. A stronger answer adds the visibility difference and chooses `public_send` as the default, explaining that `send` quietly bypasses `private` and `protected`. The strongest answer mentions that a name coming from outside the program should be checked against a fixed list before either method is called, because even `public_send` can reach any public method, including the ones every object inherits from `Object` and `Kernel`.

  • Does public_send let one instance call a protected method on another instance of the same class?
    No. A normal `other.balance` call inside the class works for a protected method, because the caller is an instance of the owning class. `public_send` ignores the caller and treats every call as coming from outside, so `other.public_send(:balance)` raises `NoMethodError` ("protected method 'balance' called"). Use a plain dot call there, or `send` if the name must be dynamic.
  • Why is using send to test a private method considered a smell?
    It works, because `send` ignores visibility, but it ties the test to an implementation detail the class chose to hide. A refactor that renames or inlines the helper breaks the test without changing behaviour. The usual fix is to test the public method that uses the helper, or to extract the helper into its own object with a public interface.

send is a master key and public_send is a visitor badge: both open the doors of the building by room name, but the badge stops at every door marked staff only.

saying these in an interview costs you the question

  • send respects private methods just like a normal dot call
  • public_send refuses private methods but still calls protected ones
  • the method name passed to send must be a Symbol, never a String
  • obj.method(:name) calls the method, the same as send
  • send and public_send are Rails helpers, not core Ruby
open as a page

In Ruby 4.0, how do you forward positional arguments, keyword arguments and a block through public_send to a method chosen at run time?

level: middleimportance: should knowfreq 38%

basics

~20 s

Collect and re-pass all three channels: def run(name, *args, **opts, &block) with public_send(name, *args, **opts, &block), or def run(name, ...) with public_send(name, ...). Collecting only *args turns keywords into a positional Hash since Ruby 3.0.

open as a page

In a Ruby admin panel that dispatches each button's action name to a service object's method, should it use send or public_send, and what else guards the dispatch?

level: seniorimportance: should knowfreq 36%

basics

~20 s

public_send, so private and protected helpers stay unreachable. Visibility alone is not an allowlist: inherited public methods such as instance_variable_set and send remain callable, so check the name against a frozen list of allowed actions before dispatching.

open as a page

In Ruby, why does BasicObject define __send__ when Kernel already provides send, and when should library code call it?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

send behaves like send but lives on BasicObject under a name nobody overrides by accident. Library code that dispatches on objects it does not control uses it, because a class may define its own send and BasicObject subclasses have no Kernel#send.

open as a page