In Ruby, how does Money.class_eval with a heredoc string differ from class_eval with a block for constants, locals and method names?
answer
- string parsed at call time
- constants: class vs lexical
- interpolated def names
- closure over objects vs names
- SyntaxError at run time
basics
~20 sA string is parsed when class_eval runs, resolves constants inside Money and can interpolate method names into def. A block is parsed with the file, keeps lexical constant lookup and closes over objects, but needs define_method for computed names.
solid answer
~40 sWith a **string**, `Money.class_eval` compiles the code at call time with `Money` as its lexical scope, so `CURRENCY` resolves to `Money::CURRENCY`, and a syntax error surfaces as `SyntaxError` only when that line runs. Interpolation lets you write `def #{unit}_amount`, which is why generated methods often use strings. The string still sees the caller's local variables by name, but objects reach it only through those locals or interpolation. With a **block**, the code is parsed with the rest of the file, and the rdoc spells out the difference: constant and class-variable lookup are not affected, so `CURRENCY` means whatever it means where the block was written. A block closes over any object, and a `def` inside still lands in `Money`, but its name must be literal, so computed names need `define_method`.
code
ruby · 11 linesCURRENCY = "EUR"
class Money
CURRENCY = "USD"
end
Money.class_eval { CURRENCY } # => "EUR" (block: lexical lookup)
Money.class_eval("CURRENCY") # => "USD" (string: looked up in Money)
factor = 100
Money.class_eval("factor") # => 100 (the string sees caller locals)go deeper
Recall that class_eval takes either a string or a block, and that a block is checked when the file loads while a string is parsed when the call runs.
Explain the constant-lookup difference with a two-line example, and why computed method names push you to a string or to define_method.
Judge when generated code belongs in a heredoc string (plain def families, with file and line) versus a block (closures, lexical lookup, tool support), and keep input out of strings.
Set a rule of thumb for a codebase: blocks by default, strings only for reviewed generators with location arguments.
## Two forms of one method `Module#class_eval` (alias `module_eval`) accepts either a **string of Ruby code** or a **block**. In both forms, `self` is the module and a bare `def` defines an instance method of it. Beyond that, the two forms differ in when the code is parsed, how names are resolved, and what the code can reach. `instance_eval` has the same string-or-block choice with the same trade-offs; the examples here use `class_eval` because that is where the question usually comes up. ## When the code is parsed - A **block** is parsed with the file that contains it. A typo is a `SyntaxError` at load time, editors and linters see ordinary code, and the compiled block is reused on every call. - A **string** is parsed each time `class_eval` runs. A typo becomes a `SyntaxError` raised by that call at run time, possibly long after deploy, and tools see only a string literal. Running the same string repeatedly repeats the parse. ## How names are resolved | Name inside the code | String form | Block form | |---|---|---| | a constant such as `CURRENCY` | looked up in `Money` first | looked up where the block was written | | a class variable such as `@@rate` | `Money`'s | the enclosing lexical scope's | | a local of the calling method | visible by name | visible (closure) | | `self` | `Money` | `Money` | | where `def` puts a method | `Money` | `Money` | The `class_eval` documentation states the block rule in one clause: the code is evaluated in the context of the module, *except that when a block is given, constant/class variable lookup is not affected*. So a top-level block that says `CURRENCY` reads the top-level constant even though `self` is `Money`, while the string `"CURRENCY"` reads `Money::CURRENCY`. A string evaluated by `class_eval` can read the caller's local variables because string evaluation compiles against the calling scope. It cannot receive arbitrary objects any other way: an object that is not held in a local must be turned into text, which works for simple literals and fails for most objects. ## What each form can express 1. **Computed method names.** `def` takes a literal name. In a string you can interpolate: `def #{unit}_amount`. In a block you must call `define_method("#{unit}_amount") { ... }` instead. 2. **Closures.** A block can capture any object — a lambda, a configuration hash, a logger — and use it from the method bodies it defines with `define_method`. A `def` in either form starts a new scope and sees no outer locals. 3. **Ordinary method bodies.** A method written with `def` inside a string is a normal method: it can `yield`, take any parameter list and uses normal `return`. That is one reason libraries that generate many methods keep using heredoc strings. ## Safety and debuggability - A string built from **untrusted input** is code injection; that concern is its own topic and the answer is to never do it. - A string has no source file unless you pass one. Without the filename and line arguments, backtraces point at an `(eval at ...)` location instead of the real code, which is why style guides require them for every string eval. - A block needs neither: its methods report the file and line where the block's code sits. ## Choosing - Use a **block** by default: parse-time errors, tool support, closures, and constant lookup that matches the surrounding code. - Use a **string** when you need what only a string gives — a family of methods written with plain `def` whose names or bodies are assembled from trusted, program-controlled parts — and pass `__FILE__` and `__LINE__ + 1`. ## Mistakes to avoid - Believing the block form resolves constants inside the class because `self` is the class. - Believing a string can see nothing of the caller; it sees locals by name. - Interpolating an object and expecting the object itself, rather than its `to_s`, to arrive. - Expecting a syntax error in a string to be caught at load time.
- Why would a library that generates dozens of accessors still choose class_eval with a heredoc string?A heredoc lets it write each method as an ordinary `def` with an interpolated name and body, so the result reads like hand-written code, can `yield`, and uses any parameter list. The trade-off is run-time parsing and no tool checking, so the names must come from the program, never from input, and the call should pass `__FILE__` and `__LINE__ + 1`.
- Inside Money.class_eval { ... } written at the top level, why does a constant defined there not end up as Money::X?Constant definition, like lookup, uses the lexical scope, and the block form does not change it. `X = 1` inside a top-level block therefore defines `Object::X`, not `Money::X`. The string form, `Money.class_eval("X = 1")`, defines it in `Money`.
saying these in an interview costs you the question
- The block form resolves constants inside the class because self is the class
- A string passed to class_eval cannot see the caller's local variables
- A syntax error in a class_eval string is reported when the file loads
- def inside a class_eval block can take an interpolated method name
- Interpolating an object into the string passes the object itself