Skip to content
Refinements
step 1/7

Reading — step 1 of 7

Learn

~3 min readClosures, Fibers, Refinements

Refinements are Ruby's solution to the monkey-patching problem: "how do I add methods to existing classes without polluting them globally?"

Monkey-patching works but affects EVERY user of the class. Refinements scope the changes to just the file where you using them. Eloquent Ruby and the Ruby docs treat this as the canonical safer alternative.

The problem refinements solve

# Monkey-patching — affects ALL String users in this process:
class String
  def shout
    upcase + "!"
  end
end

# Anywhere later: "hello".shout works.
# But ALSO: every gem that uses String now sees this method.

Fine for app code, dangerous for gems — name collisions, surprises for callers.

Refinement syntax

module StringExtensions
  refine String do
    def shout
      upcase + "!"
    end
  end
end

using StringExtensions       # activates ONLY in this file/scope

puts "hello".shout            # "HELLO!"

A refinement is a module containing one or more refine ClassName do ... end blocks. using activates the refinement in the LEXICAL SCOPE — the file from the using line to the end of the file (or method body).

Lexical scoping — the safety feature

# file_a.rb:
module MyExtensions
  refine Integer do
    def doubled
      self * 2
    end
  end
end

using MyExtensions

puts 5.doubled               # 10 — works in this file

# file_b.rb (no using):
# puts 5.doubled             # NoMethodError — refinement not active here

The refinement is invisible outside its activation scope. Other files / other gems are unaffected.

Where refinements work

  • Top-level scope of a file (after using)
  • Inside a class/module body (after using)
  • Inside individual methods (Ruby 2.4+)
class StringFormatter
  using StringExtensions     # active only inside this class
  
  def format(s)
    s.shout
  end
end

Limitations

  • Don't apply to methods called via send, public_send, or method dispatched through method_missing
  • Subtle interactions with inheritance — refinements don't propagate to subclasses unless they also using
  • Performance: slower than monkey-patching due to scope checking

Real-world use

module MyApp::CoreExt
  refine String do
    def truncate(n)
      length > n ? self[0, n - 3] + "..." : self
    end
  end
end

class UserPresenter
  using MyApp::CoreExt
  
  def display_name
    @user.name.truncate(20)
  end
end

The truncate method exists ONLY inside UserPresenter. Other code in the app — and other gems — see a clean String class.

When to use refinements vs monkey-patching

Refinements when:

  • You're writing a gem and need to extend a built-in type
  • You want lexically-scoped extensions for safety
  • You want to add the same method name as another gem might

Monkey-patching when:

  • App-level helpers you genuinely want everywhere (like ActiveSupport's Time extensions)
  • Your team has agreed on the convention and the risk is acceptable

Neither when:

  • A simple module + include works
  • A standalone helper function would do

Monkey-patching is more ergonomic. Refinements are safer. Pick based on the trust boundary: own app vs library that others will load.

Common mistakes

  • Forgetting using — the refinement is defined but inactive.
  • Expecting refinements in send — they don't apply, surprising bugs.
  • Applying using at the top of a file expecting it to affect required files — lexical means file, not load.
  • Refining a class to override an existing method — the original is shadowed only in the refinement scope; other code still sees the original. Sometimes desired, sometimes confusing.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…