# Define refinements in a module
module StringExtensions
refine String do
def titleize
split.map(&:capitalize).join(' ')
end
def to_bool
self =~ /^(true|yes|1)$/i ? true : false
end
def truncate(length, omission: '...')
return self if size <= length
self[0...(length - omission.length)] + omission
end
end
end
# Without using refinements
"hello world".titleize # NoMethodError!
# Activate refinements
using StringExtensions
"hello world".titleize # => "Hello World"
"yes".to_bool # => true
"long text".truncate(5) # => "lo..."
# Refinements are scoped to current file/module
module MyModule
using StringExtensions
def self.format(text)
text.titleize # Works here
end
end
# Won't work outside the module
"test".titleize # NoMethodError (unless using is called here too)
# Multiple refinements
module ArrayExtensions
refine Array do
def second
self[1]
end
def third
self[2]
end
def average
return 0 if empty?
sum.to_f / size
end
end
end
module HashExtensions
refine Hash do
def deep_symbolize_keys
transform_keys(&:to_sym).transform_values do |value|
value.is_a?(Hash) ? value.deep_symbolize_keys : value
end
end
end
end
using ArrayExtensions
using HashExtensions
[1, 2, 3].average # => 2.0
{ "a" => 1 }.deep_symbolize_keys # => { a: 1 }
# Refining built-in classes for domain logic
module MoneyExtensions
refine Integer do
def dollars
self * 100 # Store as cents
end
alias_method :dollar, :dollars
def cents
self
end
alias_method :cent, :cents
end
refine Numeric do
def to_currency
"$#{format('%.2f', self / 100.0)}"
end
end
end
using MoneyExtensions
price = 50.dollars + 99.cents # => 5099 cents
price.to_currency # => "$50.99"
# Time/Date refinements
module TimeExtensions
refine Integer do
def days
self * 24 * 60 * 60
end
alias_method :day, :days
def hours
self * 60 * 60
end
alias_method :hour, :hours
def minutes
self * 60
end
alias_method :minute, :minutes
def ago
Time.now - self
end
def from_now
Time.now + self
end
end
end
using TimeExtensions
5.days.ago # 5 days ago
2.hours.from_now # 2 hours from now
# Refining third-party classes
module ActiveRecordExtensions
refine ActiveRecord::Base.singleton_class do
def find_or_build(attributes)
find_by(attributes) || new(attributes)
end
def exists_or_create(attributes)
find_or_create_by(attributes)
end
end
end
using ActiveRecordExtensions
User.find_or_build(email: 'test@example.com')
# Composition of refinements
module AllExtensions
include StringExtensions
include ArrayExtensions
include HashExtensions
end
using AllExtensions
# Now all refinements are active
# Refinements in class scope
class MyService
using StringExtensions
def format_title(text)
text.titleize # Works within class
end
end
# Testing refinements
RSpec.describe "String refinements" do
using StringExtensions
it "titleizes strings" do
expect("hello world".titleize).to eq("Hello World")
end
it "converts to boolean" do
expect("yes".to_bool).to be true
expect("no".to_bool).to be false
end
end
# Refinements don't affect method lookup in weird ways
module RefinementExample
refine String do
def reverse
"refined reverse: #{super}"
end
end
end
using RefinementExample
"hello".reverse # => "refined reverse: olleh"
# Refinements with inheritance
class Base
def process(text)
text.upcase
end
end
class Derived < Base
using StringExtensions
def process(text)
text.titleize # Refinement works
end
end
# Limitations of refinements
# - Don't work with send/public_send/__send__
# - Don't work with method_missing
# - Don't work in method defined before 'using'
# - Can't refine singleton classes
Refinements provide scoped modifications to existing classes without global monkey patching. I use refinements to add methods to core classes safely. Refinements activate with using statement—scope-limited to file or module. Unlike monkey patches, refinements don't affect other code. Refinements are lexically scoped—only active where explicitly used. I prefer refinements over monkey patching for maintainability. Refinements enable extending libraries without side effects. Understanding refinement scope rules prevents surprises. Refinements work in modules, classes, and at file scope. They're Ruby's solution to safe class extension. Refinements balance Ruby's flexibility with predictability.