In Ruby 4.0, why must a refine block reuse a helper module's methods through import_methods rather than include, and what can import_methods not copy?
answer
- refine block's self is a Refinement
- include there raises TypeError
- added in 3.1
- copies methods, rebinds scope
- C methods raise ArgumentError
basics
~20 sInside refine, self is a Refinement whose include and prepend now raise TypeError. Refinement#import_methods, added in Ruby 3.1, copies a module's Ruby-defined methods into the refinement; C-implemented methods raise ArgumentError, and the module's ancestors are skipped with a warning.
solid answer
~50 sA `refine` block evaluates with `self` set to a `Refinement`. Before Ruby 3.1 people wrote `include SlugRules` there to reuse methods; 3.1 made `Refinement` its own class, deprecated `include` and `prepend` inside it and added `import_methods`, and in Ruby 4.0 `include` raises `TypeError` ("Refinement#include has been removed"). The reason is scope: an included module's methods keep the lexical scope of the file where they were written, so calls inside them would not see the refinement. `import_methods SlugRules` **copies** each method into the refinement and gives the copy the refinement scope of the `refine` block, so a copied `slug_with_id` can call the refined `to_slug`. Because it copies bytecode, only methods written in Ruby can be imported — a C method raises `ArgumentError` — and methods of the module's own ancestors are not imported, with a warning.
code
ruby · 13 linesmodule SlugRules
def to_slug = downcase.gsub(/[^a-z0-9]+/, "-").delete_prefix("-").delete_suffix("-")
def slug_with_id(id) = "#{id}-#{to_slug}"
end
module Slugs
refine String do
import_methods SlugRules
end
end
using Slugs
"Big News".slug_with_id(7) # => "7-big-news"go deeper
Remember that inside refine you reuse a module with import_methods, not include, and that only Ruby-written methods can be imported.
Explain that import_methods copies methods and gives them the refinement's scope, which is why their internal calls to refined methods work.
Plan the migration of pre-3.1 code that used include inside refine, and account for snapshot copying and skipped ancestors when splitting helpers into modules.
Judge whether a refinement large enough to need imported modules should stay a refinement, or whether a plain wrapper object would be clearer to maintain.
## The problem import_methods solves A refinement's methods are usually written inline in the `refine` block. Sometimes the same helpers already exist in a plain module — `SlugRules`, say — and you want a refinement of `String` to offer them. The obvious move, `include SlugRules` inside the block, used to work, but it had a flaw rooted in lexical scoping: an included method runs with the refinement scope **of the file and body where it was written**. If `SlugRules#slug_with_id` called `to_slug`, and `to_slug` existed only as a refinement, the call inside the included method would not see it. ## What changed, by version | Ruby | Behaviour inside a `refine` block | |---|---| | 2.x – 3.0 | `include`/`prepend` add modules to the refinement | | 3.1 | `Refinement` becomes a class; `include`/`prepend` deprecated; `import_methods` added | | 3.2 | `Module#refinements` and `Module.used_refinements` added | | 3.3 | `Refinement#target` added, `refined_class` deprecated | | 3.4 | `Refinement#refined_class` removed | | 4.0 | `include` raises `TypeError` "Refinement#include has been removed"; `prepend` likewise | ## How import_methods works 1. You call `import_methods SlugRules` (several modules may be listed) inside `refine String do ... end`. 2. For each method in the module's own method table, Ruby checks that it was defined in Ruby code. 3. It **copies** the method into the refinement, keeping its visibility. 4. It gives the copy the refinements that are active at the `import_methods` call, which includes the refinement module's own refinements. The result is that copied methods behave as if they had been written inside the `refine` block: calls in them to other refined methods work. ## What it cannot copy - **Methods implemented in C.** Copying needs the method's Ruby bytecode, so a method implemented in C raises `ArgumentError` ("Can't import method which is not defined with Ruby code"). Importing a core module such as `Enumerable` fails for this reason. - **Methods of the module's ancestors.** Only the module's own methods are copied. If the module includes other modules, Ruby warns that it "has ancestors, but Refinement#import_methods doesn't import their methods". Import those modules explicitly. - **Later additions.** Because methods are copied at the moment of the call, a method added to `SlugRules` afterwards does not appear in the refinement. ## Other Refinement reflection - `Module#refinements` returns the `Refinement` objects a module defines. - `Refinement#target` returns the class or module being refined. - `Module.used_refinements` lists the refinements active in the current scope. ## Migrating code written before 3.1 Code that still uses `include` inside `refine` fails to load on Ruby 4.0. A safe migration: 1. Replace `include SomeHelpers` inside each `refine` block with `import_methods SomeHelpers`. 2. If `SomeHelpers` itself includes other modules, list those modules in the `import_methods` call too, since their methods are not copied. 3. If any helper method is implemented in C, keep it out of the imported module and define a Ruby wrapper inline instead. 4. Re-run the tests that exercise the refined methods, particularly helpers that call other refined methods, which now see the refinement. 5. Replace any `refined_class` call with `target`. ## When to reach for it - Sharing one set of Ruby-written helpers between a refinement and ordinary classes. - Splitting a large refinement into topical modules imported into one `refine` block. - Migrating code written before Ruby 3.1 that used `include` inside `refine`, which fails in Ruby 4.0. For small refinements, defining the methods inline remains simpler and avoids the copy-time semantics entirely.
- In Ruby 4.0, if SlugRules gains a new method after import_methods ran, does the refinement get it?No. `import_methods` copies the module's methods at the moment it runs, so the refinement holds snapshots. A method added to `SlugRules` later, or a redefinition of an existing one, is not reflected; the `refine` block must run `import_methods` again or define the method itself.
- In Ruby 4.0, what does Refinement#target return, and what did it replace?It returns the class or module the refinement refines, for example `String`. It was added in Ruby 3.3 as the replacement for `Refinement#refined_class`, which 3.3 deprecated and 3.4 removed.
saying these in an interview costs you the question
- Writes include SlugRules inside refine and expects it to work in Ruby 4.0.
- Believes import_methods links the module, so later edits appear in the refinement.
- Tries import_methods Enumerable to add C-implemented core methods to a refinement.
- Assumes import_methods also copies methods from the module's included modules.
- Still calls Refinement#refined_class, which Ruby 3.4 removed.