Reading — step 1 of 7
Learn
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 throughmethod_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
usingat 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…