In Ruby, how would you generate paid?, shipped? and cancelled? predicate methods on an Order class from a list of its statuses?
answer
- iterate a frozen list
- interpolated Symbol as the name
- each gives a fresh variable
- for shares one variable
- every predicate checks the last status
basics
~10 sLoop over a frozen STATUSES list with each and call define_method(:"#{s}?") { status == s } for every entry. Each iteration's block captures its own s, so every predicate compares against its own status.
solid answer
~40 sI keep the statuses in a frozen constant and generate the predicates in the class body: `STATUSES.each { |s| define_method(:"#{s}?") { status == s } }`. The name is built by interpolation, and the method body is a closure over `s`. Because `each` gives every iteration a fresh block parameter, `paid?` keeps `:paid` and `shipped?` keeps `:shipped`. Writing the same thing with a `for` loop is a classic bug: `for` does not open a new scope, so all the closures share one variable and every predicate ends up comparing with the last status. I would add a test that checks each predicate against every status.
code
ruby · 13 linesclass Order
STATUSES = %i[pending paid shipped cancelled].freeze
attr_reader :status
def initialize(status) = @status = status
STATUSES.each do |s|
define_method(:"#{s}?") { status == s }
end
end
Order.new(:shipped).shipped? # => true
Order.new(:shipped).paid? # => falsego deeper
Recall the shape: each over a list, an interpolated name, and define_method with a block that compares the status.
Explain why each iteration keeps its own value while a for loop shares one variable, and what that does to the predicates.
Show that you test generated families against the full list and keep them discoverable for readers who search for def.
Decide when a generated family earns its keep over writing a handful of methods by hand.
## The task An `Order` has a `status` that is one of a fixed set: `pending`, `paid`, `shipped`, `cancelled`. Callers want to write `order.paid?` rather than `order.status == :paid`. Writing four near-identical methods by hand works, but the list grows, and every new status needs a matching method that someone may forget. **Generating** the predicates from the list keeps the two in step. ## The idiomatic version ```ruby class Order STATUSES = %i[pending paid shipped cancelled].freeze attr_reader :status def initialize(status) = @status = status STATUSES.each do |s| define_method(:"#{s}?") { status == s } end end Order.new(:paid).paid? # => true Order.new(:paid).shipped? # => false ``` How it works, step by step: 1. `STATUSES.each` runs the block once per status, while the class body is being executed. 2. `:"#{s}?"` builds the method name, such as `:paid?`. A String would work too. 3. `define_method` adds an instance method with that name. Its body is the inner block. 4. The inner block is a **closure** over `s`. Because `each` gives every iteration a **fresh** block parameter, each generated method keeps its own `s`. 5. Inside the method body, `self` is the order, so `status` calls the reader. The generated methods are ordinary public instance methods: `Order.instance_methods(false)` lists them, and `Order.new(:paid).respond_to?(:paid?)` is `true`. ## The loop that breaks it The same code with a `for` loop looks equivalent and is not: ```ruby for s in STATUSES define_method(:"#{s}?") { status == s } end Order.new(:paid).paid? # => false Order.new(:cancelled).paid? # => true ``` A `for` loop does **not** create a new variable scope. There is one `s`, a local of the class body, and every iteration assigns to it. All four closures capture that **same** variable, so after the loop they all compare against its final value, `:cancelled`. The method names are right (they were computed during each iteration), but every body answers the question "is it cancelled?". The same trap appears with `while` loops or with a counter updated outside the block. | Loop | Variable per iteration | Result | |---|---|---| | `STATUSES.each { \|s\| ... }` | fresh `s` each time | each predicate checks its own status | | `for s in STATUSES` | one shared `s` | every predicate checks the last status | ## Details worth getting right - **Freeze the list.** `STATUSES` is read by the generator and usually by validations; freezing it stops a caller from adding a status that has no predicate. - **Keep the closure small.** The block captures the whole local scope of the class body, not just `s`. Avoid defining large temporary objects as locals in the same scope. - **Mind collisions.** A generated name can overwrite a method of the same name defined earlier in the class; generate from a list you control. - **Document the family.** A text search for `def paid?` finds nothing. A comment above the loop listing the generated names, or a test that checks every status has its predicate, helps the next reader. ## Alternatives and their trade-offs - **Hand-written methods.** For three or four statuses that rarely change, plain `def paid? = status == :paid` lines are the most readable and are found by any search. - **A single query method.** `order.status?(:paid)` avoids generation entirely, at the cost of a less fluent call site. - **Generation with `define_method`.** Best when the list is long, changes often, or already drives validations and user interface choices. ## Testing the family ```ruby Order::STATUSES.each do |s| order = Order.new(s) raise "#{s}? wrong" unless order.public_send(:"#{s}?") others = Order::STATUSES - [s] raise "#{s} leaks" if others.any? { order.public_send(:"#{it}?") } end ``` A loop like this catches the `for` bug at once, because it checks each predicate against every status. ## What an interviewer is listening for A junior answer shows `each` plus `define_method` with an interpolated name. A good answer explains why each method keeps its own status value, and a very good one volunteers the `for` loop failure without being asked.
- Why does the method name come out right even in the broken for-loop version?The name is computed immediately, while `define_method` is being called in that iteration, so `:"#{s}?"` sees the current value. Only the body is deferred: it reads `s` later, when the method is called, and by then the single shared variable holds the last status.
- How would you make the generated predicates private?Call `private` before the loop in the class body; `define_method` running there picks up the default visibility, just like `def`. Alternatively pass the names to `private` after the loop, since `private` accepts a list of method names.
Each iteration of each hands a different envelope to each new method, while a for loop hands them all a photocopy of the same whiteboard that keeps being rewritten until the loop ends.
saying these in an interview costs you the question
- a for loop and each capture loop values the same way
- each closure copies the variable's value when it is created
- define_method needs a String name, so a Symbol must be converted
- the predicates are generated every time Order.new runs
- generated methods do not appear in instance_methods