skip to content

In Ruby, how do refine and using add a String#to_slug method without patching String for the whole program?

level: juniorimportance: nice to knowfreq 25%

answer

  1. refine lives inside a module
  2. block receives a Refinement
  3. using switches it on
  4. active until end of file or body
  5. not allowed inside a method

basics

~20 s

Define the method inside refine String do ... end in a module, then call using with that module. Only code after that using line sees it, up to the end of the file or the class or module body.

solid answer

~40 s

A refinement is declared in a module: `module Slugs; refine String do ... end; end`. `refine` is a private method of `Module` that `Class` undefines, so it only works in a module body, and it needs a block, in which `self` is an anonymous `Refinement` that collects the new methods. Nothing changes until some code calls `using Slugs`. At the top level of a file (`main.using`), the refinement is active from that line to the end of the file; inside a class or module body (`Module#using`), from that line to the end of that body. Code above the `using` line, other files and gems still see the unrefined `String`. `using` is rejected inside a method with `RuntimeError`, and `super` inside a refined method reaches the original `String` method.

code

ruby · 11 lines
ruby
module Slugs
  refine String do
    def to_slug = downcase.gsub(/[^a-z0-9]+/, "-").delete_prefix("-").delete_suffix("-")
  end
end

# "Hello, World!".to_slug here would raise NoMethodError

using Slugs

"Hello, World!".to_slug   # => "hello-world"

go deeper

for a junior

Remember the two steps: refine String do ... end inside a module, then using that module; only code after using, in that file or body, sees the method.

for a middle

Explain the scopes: main.using lasts to the end of the file, Module#using to the end of the class or module body, and using inside a method raises RuntimeError.

for a senior

Contrast refinements with global reopening: explain when scoped activation prevents library conflicts and when its invisibility to other code makes it the wrong tool.

for a principal

Frame refinements as a policy choice for core extensions: scoped by default, with global patches reserved for cases that must affect every caller.

## Why refinements exist Ruby classes are **open**: any file can reopen `String` and add or replace a method, and every other piece of code in the process sees the change. That global reach is what makes such patches risky — two libraries can define the same method differently, and the last one loaded wins everywhere. **Refinements** are Ruby's scoped alternative: they change a class only for code that explicitly opts in. (Global reopening and patch strategies are a separate topic.) ## The two halves: refine and using 1. **Declare** the change in a module with `refine TargetClass do ... end`. The methods defined inside the block form the refinement. 2. **Activate** it with `using ThatModule` where you want it. Facts about each half: - `refine` is a private method of `Module`. `Class` undefines it, so calling `refine` directly in a class body raises `NoMethodError`; refinements always live in modules. - `refine` needs a block (without one it raises `ArgumentError`). Inside the block `self` is an anonymous module of class `Refinement`, similar to the receiver of `module_eval`. - One module may contain several `refine` blocks for different classes, and `using` activates all of them together. - `refine` also accepts a module as its target (since Ruby 2.4), not only a class. ## Where using is allowed | Where `using` appears | Which method runs | Refinement active until | |---|---|---| | top level of a file | `main.using` | the end of that file | | directly in a class or module body | `Module#using` | the end of that body | | a string passed to `eval` | `main.using` or `Module#using` | the end of that string | | inside a method | — | not allowed: `RuntimeError` | The effect starts **at the line where `using` runs**, because `using` is an ordinary method call. Code earlier in the same file is unaffected, and a `using` inside `if false` never activates anything. Reopening the same class in another body does not bring the refinement along; each body must call `using` itself. ## What a refined method can do Inside a refined method `self` is the receiver, as in any instance method, so `downcase` and `gsub` in `to_slug` are ordinary `String` calls. Calling `super` from a refined method that overrides an existing one reaches the refined class's original method, which makes wrapping behaviour possible without touching the original. ## Refinements versus a global patch - A global patch changes the class for every caller in the process, including gems. - A refinement changes it only for call sites written after `using` in the activating file or body. - A global patch is visible to introspection such as `String.instance_methods`; a refined-only method is not. ## Mistakes to avoid - Writing `refine` in a class body: `Class` undefines it, so the call raises `NoMethodError`. - Calling `using` inside a method in the hope of a narrow scope: Ruby raises `RuntimeError`. - Placing `using` below the code that needs it: activation starts at the `using` line. - Expecting a `require`d file to inherit the activation: every file must call `using` on its own. - Forgetting that `refine` without a block raises `ArgumentError`. A good habit is to put `using` lines directly under the `require` lines at the top of a file, so a reader sees at a glance which refinements the whole file relies on. ## Where the scenario lands For a slug helper, `using Slugs` at the top of the one file that builds URLs keeps `to_slug` out of every other file, every gem and every console session that did not ask for it. If two teams both want a `to_slug` with different rules, each file picks its own module, and neither patch leaks into the other.

  • In Ruby, what happens when using Slugs is called inside a method body?
    Ruby raises `RuntimeError` ("Module#using is not permitted in methods", or "main.using is permitted only at toplevel" for the top-level form). Refinements are fixed per file, class body or module body when that code is loaded, so they cannot be switched on per call.
  • In Ruby, can one refinement module refine several classes at once?
    Yes. A module may hold several `refine` blocks, for example for `Integer`, `Array` and `Hash`, and one `using` activates all of them. Refined methods defined in the same module can call one another, because all of the module's refinements are active inside each refined method.

saying these in an interview costs you the question

  • Calls refine directly inside a class body instead of a module.
  • Believes using inside a method limits the refinement to that method.
  • Thinks once any file calls using, String is refined program-wide.
  • Expects code above the using line in the same file to see the refinement.
  • Assumes a refinement also changes String inside loaded gems.