Refinements — Scoped Monkey Patching

· 3 min read · Updated April 5, 2026 · intermediate
refinements monkey-patching metaprogramming scoped

Monkey patching is a fact of life in Ruby — sometimes you just need String to have a method it doesn’t. The problem is that patching String globally affects every piece of code in your project, including code you don’t control. Refinements solve this by limiting your patches to a specific lexical scope.

Defining a Refinement

Use Module#refine inside a module to define what you’re changing:

module StringExtensions
  refine String do
    def shout
      "#{self.upcase}!"
    end

    def title_case
      split.map(&:capitalize).join(" ")
    end
  end
end

refine creates an anonymous module holding your modifications. It doesn’t affect anything until you activate it.

Activating with using

using StringExtensions

"hello world".shout       # => "HELLO WORLD!"
"hello world".title_case  # => "Hello World"

using activates the refinements in the current lexical scope — from that line to the end of the file, or to the end of the current class/module definition.

Outside that scope, the original String behavior applies:

using StringExtensions

"hello".shout  # => "HELLO!"

# This file doesn't have `using`, so refinements are not active here
require "./other_file"  # other_file sees original String

Scope Rules

Refinements are lexical. That has several practical consequences:

You can’t activate them inside a method:

class MyClass
  def activate_shout
    using StringExtensions  # SyntaxError: refinements cannot be used here
  end
end

Refinements are active until the end of the class or file:

module MyHelpers
  using StringExtensions

  def self.process(text)
    text.shout  # refinement is active here
  end
end

"hello".shout  # NoMethodError — outside the module scope

Reopening a class doesn’t reactivate your refinement:

module MyExtensions
  refine Integer do
    def factorial
      self <= 1 ? 1 : self * (self - 1).factorial
    end
  end
end

using MyExtensions

class Foo
  5.factorial  # works — inside same file as `using`
end

class Foo
  10.factorial  # still works — same top-level lexical scope
end

Method Lookup Order

When Ruby looks up a method on an object, it checks in this order:

  1. Refinements (most recently activated takes priority)
  2. Prepended modules
  3. The class itself
  4. Ancestors

Refinements always win over prepended modules, and prepended modules always win over the class. This means your refinement takes precedence over both a prepend and the original method.

Refinements in Practice

One common use is test helpers or debugging tools that want to add methods without polluting global scope:

module DebugRefinements
  refine Hash do
    def inspect_keys(*keys)
      select { |k, _| keys.include?(k) }.inspect
    end
  end
end

using DebugRefinements

{ name: "Alice", age: 30, role: "admin" }.inspect_keys(:name, :role)
# => "{:name=>\"Alice\", :role=>\"admin\"}"

Gotchas

Code before using doesn’t get the refinement:

"hello".shout  # NoMethodError
using StringExtensions
"hello".shout  # "HELLO!"

Refinements don’t affect BasicObject methods. You can’t refine methods like object_id or send that exist on BasicObject.

Performance is slower than direct method calls. Each method lookup in a refined scope adds overhead. Negligible in most application code, measurable in tight loops.

Calling self inside a refinement uses the refinement’s version:

module MyRefinements
  refine String do
    def shout
      # If upcase is also refined, this calls the refined upcase
      "#{upcase}!"
    end
  end
end

When to Use Refinements

Refinements are worth reaching for when:

  • You’re writing a test helper that adds debugging methods — keep it in the test file
  • You’re building a DSL and want clean syntax without polluting the global namespace
  • You need to temporarily patch a gem’s behavior without affecting other parts of your codebase

For permanent application-level patches to core classes, a standard monkey patch in an initializer is simpler and faster.

See Also