In Ruby, why does a String refinement activated with using in one file raise NoMethodError when a helper from another file calls it?
answer
- lexical, not dynamic
- the call site decides
- definition place travels with the method
- reopened class starts unrefined
- last using wins on a clash
basics
~20 sRefinements are lexically scoped: a call sees a refinement only if its source text sits after a using in the same file or body. The helper's call was written in another file, so it looks up plain String and misses.
solid answer
~40 sRuby decides whether a refinement applies by where the calling code is written, not by who is running at the moment. `using Slugs` in `app.rb` refines the call sites in `app.rb` after that line; a helper in `slugger.rb` that calls `text.to_slug` was compiled without that activation, so the lookup ignores the refinement and raises `NoMethodError`, even when `app.rb` is the caller. The reverse also holds: a method written in a refined scope keeps the refinement wherever it is called from, so `Article#slug` defined after `using Slugs` works for every caller. Blocks written in the refined file carry it too. Reopening `Article` elsewhere starts unrefined, and when two active refinements define the same method, the most recently activated one wins.
code
ruby · 13 lines# slugger.rb - no using in this file
module Slugger
def self.call(text) = text.to_slug
end
# app.rb
require_relative "slugs"
require_relative "slugger"
using Slugs
"Big News".to_slug # => "big-news"
["Big News"].map { it.to_slug } # => ["big-news"]
Slugger.call("Big News") # NoMethodError, raised inside slugger.rbgo deeper
Remember that a refinement applies where the calling code is written, after using, not wherever it happens to run.
Explain why a helper in another file raises NoMethodError while a method defined in the refined file keeps working for every caller.
Use the lexical rule to decide where refinements fit: safe inside a library, useless for changing third-party behaviour, and dependent on activation order when they overlap.
Weigh refinements against global patches as an organisational rule: lexical scoping protects shared code but spreads using lines that reviewers must track.
## Lexical scope in one sentence A **lexically scoped** feature depends on where code is *written*; a **dynamically scoped** one depends on who is *calling* at run time. Ruby refinements are lexical: whether `text.to_slug` sees the `Slugs` refinement is decided by whether that call's source text follows a `using Slugs` in the same file, class body or module body. ## The classic surprise 1. `slugs.rb` declares `refine String` with `to_slug`. 2. `slugger.rb` defines `Slugger.call(text)`, which returns `text.to_slug`. It has no `using`. 3. `app.rb` requires both files, runs `using Slugs`, and calls `Slugger.call("Hello World")`. 4. Ruby raises `NoMethodError` inside `Slugger.call`, because that call site in `slugger.rb` was never refined. The caller's activation does not flow into the callee. This is deliberate: a library cannot be broken by refinements that its callers happen to activate. ## What does carry the refinement | Code | Refined? | Why | |---|---|---| | a call written after `using` in the same file | yes | the call site is inside the activated scope | | a method defined after `using`, called from another file | yes | the method's body was written in the activated scope | | a block written after `using`, passed to a method elsewhere | yes | the block's body is written in the activated scope | | a method in another file, called from the refined file | no | its body was written outside the scope | | a later reopening of the same class without its own `using` | no | each class body activates separately | | code in a file loaded with `require` after `using` | no | refinements do not cross file boundaries | The second row is the useful half of the rule: a class can `using Slugs` inside its own body and offer methods such as `Article#slug` that work for every caller, while the rest of the program never sees `String#to_slug`. ## Precedence when refinements overlap - **Several refinements of the same class**: the refinements active at a call site are searched in reverse order of activation, so the most recent `using` wins. - **Refinements versus the class hierarchy**: lookup checks, for each class in turn, its refinements, then its prepended modules, the class itself and its included modules, before moving to the superclass. A method defined in a **subclass** therefore beats a refinement of a **superclass**: refining `Numeric#/` does not change `1 / 2`, because `Integer#/` is found first. A refinement of `Numeric` that adds a method `Integer` lacks does apply to integers. - **`super` in a refined method** calls the refined class's original method, even if another refinement of it is active in the same scope. ## Why Ruby chose lexical scope Dynamic scoping would let any caller change how code it calls behaves. A library method that calls `to_s` could return something different depending on which application file happened to call it — exactly the global-patch problem refinements were designed to avoid. With lexical scope, the author of each file controls which refinements that file's code sees, and a reader can tell by looking at the `using` lines above the code. The cost is the surprise in this question: refinements do not flow into helpers, so a helper that depends on a refined method must activate the refinement itself. ## Consequences for design - Refinements cannot change how third-party code behaves: a gem that calls `to_s` internally keeps calling the unrefined method. - Helpers that rely on refined methods must activate the refinement in their own file. - Methods that a library defines and later calls on your objects run in the library's scope, not yours. - A refinement never leaks into other files, which is exactly why it is safe to use in a library. ## Debugging checklist - Find the file and body where the failing call is *written*, not the caller. - Check that a `using` for the right module appears above it in that scope. - If the call lives in a reopened class, add `using` to that body as well. - If behaviour differs between two refinements, check which `using` ran last.
- In Ruby, if a refinement of Numeric defines /, what does 1 / 2 call?`Integer#/`. Lookup handles one class at a time: for `Integer` it checks Integer's refinements, prepends, the class and its includes, and finds `Integer#/` before it ever reaches `Numeric` and its refinements. A refined `Numeric` method that `Integer` does not define would still be used for integers.
- In Ruby, two modules refine String#to_slug differently and one file calls using on both; which runs?The one activated last. Active refinements are searched in reverse order of activation, so the later `using` wins for call sites after it. Inside that refined method, `super` goes to the original `String` method, not to the other refinement.
A dialect learned in one classroom: students taught there keep using the new words wherever they later go, but a student from another classroom never learned them, no matter who asks the question.
saying these in an interview costs you the question
- Believes the caller's using makes the refinement visible inside called methods.
- Thinks a method defined in a refined scope loses the refinement when called elsewhere.
- Expects a reopened class body to inherit an earlier body's using.
- Assumes refinements can change how a gem's internal calls behave.
- Says a refinement of Numeric#/ changes the result of 1 / 2.