In Ruby, what does calling method(:format_row) on an object return, and how do you invoke the result later?
answer
- an object, not a result
- bound to its receiver
- call, .() and []
- private methods too; NameError if missing
- name, receiver, owner
basics
~10 sKernel#method returns a Method object: format_row bound to that receiver, without running it. Invoke it later with call, .() or [], passing arguments as usual; an unknown name raises NameError.
solid answer
~30 s`report.method(:format_row)` looks the method up the way a call on `report` would and wraps the definition together with its receiver in a `Method` object; nothing runs yet. Later, `m.call(row)`, `m.(row)` and `m[row]` all run it with `self` set to `report`, so the body can use `report`'s instance variables. `method` finds private methods as well, while `public_method` returns public ones only; an unknown name raises `NameError`. The object also answers questions about itself: `name` is `:format_row`, `receiver` is `report`, and `owner` is the class or module that defines the method.
code
ruby · 19 linesclass PriceSheet
def initialize(currency)
@currency = currency
end
def format_row(row) = "#{row[:sku]}: #{row[:price]} #{@currency}"
end
sheet = PriceSheet.new("EUR")
m = sheet.method(:format_row) # nothing runs yet
m.call({sku: "A1", price: 9.5}) # => "A1: 9.5 EUR"
m.({sku: "B2", price: 3}) # => "B2: 3 EUR"
m[{sku: "C3", price: 1}] # => "C3: 1 EUR"
m.name # => :format_row
m.receiver # => the sheet
m.owner # => PriceSheet
sheet.method(:nope) # NameErrorgo deeper
Remember that method(:name) returns an object you call later with call, .() or [], and that it runs on the object you took it from.
Explain what the Method holds, the receiver and the definition, and the difference between method and public_method, name, receiver and owner.
Use Method objects to hand existing, tested behaviour to APIs that accept any callable, and know that lookup errors surface immediately as NameError.
Decide when public APIs should accept any object responding to call, which lets callers pass Method objects, lambdas or small classes interchangeably.
## Getting a Method object In Ruby a method is not normally a value you can hold: `report.format_row(row)` simply calls it. **`Kernel#method`** turns a method into an object you can store and pass around. `report.method(:format_row)` (a Symbol or a String works) finds `format_row` the same way a call on `report` would, through the object's singleton class, its class, included modules and superclasses, and returns an instance of **`Method`**. That object holds two things: - the **definition** it found, and - the **receiver**, `report`, the object the method will run on. Nothing has been executed. The core documentation describes a `Method` as acting like a closure over its object: instance variables and `self` stay available whenever it runs. ## Calling it | Form | Meaning | |---|---| | `m.call(row)` | the explicit call | | `m.(row)` | syntax sugar for `call` | | `m[row]` | alias of `call` | | `m === row` | also calls it, which lets a `Method` appear in a `when` clause | The `call`, `.()` and `[]` forms pass positional arguments, keyword arguments and a block through to the method exactly as a direct call would. The number of arguments is checked like any method call, so a wrong count raises `ArgumentError`. ## Lookup rules 1. **Private methods are included.** `obj.method(:helper)` returns a `Method` even if `helper` is private, and calling it works, because you went through the object deliberately. 2. **`public_method`** is the strict variant: it raises `NameError` for private and protected methods. 3. **Unknown names raise `NameError`** at lookup time, not later when the object is called, which surfaces a typo where it was made. 4. The receiver can be any object, including a class: `Integer.method(:sqrt)` wraps a class method. ## Asking the object about itself - `m.name` returns the Symbol, `:format_row`. - `m.receiver` returns the bound object, `report`. - `m.owner` returns the class or module where the definition lives, which may be a module mixed into `report`'s class. - `m.unbind` returns an `UnboundMethod` that has forgotten the receiver, which can later be bound to another compatible object. - `m.arity` and `m.parameters` describe the parameters. ## Lookup happens once The `Method` object keeps the definition it found when `method` was called: - A typo fails immediately with `NameError`, at the line that asked for the method, rather than later inside the code that calls it. - If the class later **redefines** `format_row`, a `Method` taken earlier still runs the old body, while a fresh `sheet.format_row(row)` runs the new one. Take the `Method` again after a reload if you need the new code. - Each call to `method(:format_row)` performs the lookup and allocates a new `Method`, so take it once and keep it when it will be called many times. ## Why a report generator cares Suppose a `ReportGenerator` accepts anything that responds to `call` as its row formatter. An existing, tested `PriceSheet#format_row` can be handed over as `sheet.method(:format_row)` without writing a wrapper block, and the generator stays unaware of whether it got a `Method`, a lambda or a custom object with a `call` method. Walking through it: 1. `sheet.method(:format_row)` finds `PriceSheet#format_row` and binds it to `sheet`. 2. The generator stores the object and, for each row, calls `@formatter.call(row)`. 3. Each call runs `format_row` with `self` equal to `sheet`, so `@currency` is available. 4. The generator never needed to know the method's name or its class. ## Common slips - Writing `method(:format_row)` and expecting the formatted string: it returns the `Method`, not a result. - Expecting `m.unbind.call(row)` to work: an `UnboundMethod` has no `call` until it is bound again. - Assuming `method` refuses private methods; that is `public_method`.
- What is the difference between Method#owner and Method#receiver?`receiver` is the object the method is bound to and will run on. `owner` is the class or module whose method table holds the definition. For a method mixed in from a module, `owner` is that module even though `receiver` is an instance of some class that includes it.
- Does obj.method(:helper) work when helper is private?Yes. `Kernel#method` returns private and protected methods as well, and calling the resulting `Method` runs them. Use `public_method` when you want lookup to fail for anything that is not public, for example when the name comes from outside.
saying these in an interview costs you the question
- method(:format_row) calls the method and returns its result
- A Method object forgets its receiver until call is given one
- method raises NameError for private methods
- An UnboundMethod from unbind can be called directly
- A Method object takes any number of arguments, like a block