Open Class / Refinements (Ruby)
Ruby's open class feature allows methods to be added to existing classes — including built-in classes like String and Integer — at any time. This page covers monkey-patching examples, the convenience and risks involved, and Refinements as a safer, scope-limited extension approach.
Syntax
Syntax for extending an existing class using open class.
# Reopen an existing class and add a method.
class ExistingClassName
def new_method_name
# implementation
end
end
# Example: adding a method to the String class.
class String
def new_method_name
# implementation
end
end
Syntax for a scope-limited extension using Refinements.
# Define a scope-limited extension with refine inside a module.
module ModuleName
refine TargetClass do
def new_method_name
# implementation
end
end
end
# Activate the Refinements only within the current file or module.
using ModuleName
Open Class Overview
| Syntax / Keyword | Description |
|---|---|
| class ExistingClassName | Reopens an existing class and adds methods. This is global monkey-patching. |
| module ModuleName / refine ClassName do | Defines Refinements. The extension is active only within the scope where using is called. |
| using ModuleName | Activates Refinements. The scope is limited to the file or module where it is called. |
| method_defined?(:method_name) | Checks whether a method is defined on a class. Useful for verifying no accidental overwrites when using open class. |
| instance_method(:method_name) | Retrieves an instance method as an UnboundMethod object. |
Sample Code
Extending the String class with open class to add convenience methods for Evangelion character dialogue.
open_class_string.rb
# Extend the String class using open class.
class String
# Appends an exclamation mark to the string.
def shout
self + "!"
end
# Wraps the string in quotation marks.
def quote
"\"#{self}\""
end
# Returns true if the string contains only whitespace (or is empty).
def blank?
strip.empty?
end
end
# Use the added methods with character dialogue.
shinji = "I mustn't run away"
rei = "I can do anything"
asuka = "What an idiot"
misato = "All right, let's go"
kaworu = "You can kill me"
puts shinji.shout # I mustn't run away!
puts rei.quote # "I can do anything"
puts asuka.shout.quote # "What an idiot!"
puts misato.quote.shout # "All right, let's go"!
puts kaworu.blank? # false
puts "".blank? # true
puts " ".blank? # true
ruby open_class_string.rb I mustn't run away! "I can do anything" "What an idiot!" "All right, let's go"! false true true
Extending the Integer class to add useful methods based on pilot numbers.
open_class_integer.rb
# Extend the Integer class using open class.
class Integer
# Converts the number to an "Apostle #N" string.
def to_apostle
"Apostle ##{self}"
end
# Repeats a string the given number of times.
def repeat_str(str)
str * self
end
end
# Display pilot numbers as apostle designations.
pilot_numbers = { "Shinji Ikari" => 1, "Rei Ayanami" => 2, "Asuka Langley Soryu" => 3 }
pilot_numbers.each do |name, number|
puts "#{name}: #{number.to_apostle}"
end
# Generate a separator line using the repeat method.
puts 20.repeat_str("-")
puts "Kaworu Nagisa is #{17.to_apostle}."
ruby open_class_integer.rb Shinji Ikari: Apostle #1 Rei Ayanami: Apostle #2 Asuka Langley Soryu: Apostle #3 -------------------- Kaworu Nagisa is Apostle #17.
Demonstrating the risks of open class. Accidentally overwriting an existing method causes unexpected behavior.
open_class_risk.rb
# Check the original behavior before overwriting.
puts "NERV".upcase # NERV (original behavior)
puts "nerv".upcase # NERV
# Danger: this overwrites the existing upcase method.
class String
def upcase
"[WARNING: upcase has been sealed]"
end
end
# After overwriting, all String instances are affected.
puts "NERV".upcase # [WARNING: upcase has been sealed]
puts "nerv".upcase # [WARNING: upcase has been sealed]
puts "Shinji Ikari".upcase # [WARNING: upcase has been sealed]
# method_defined? can be used to check for existing methods before adding.
puts String.method_defined?(:upcase) # true (already defined)
puts String.method_defined?(:shout) # false (not defined)
ruby open_class_risk.rb NERV NERV [WARNING: upcase has been sealed] [WARNING: upcase has been sealed] [WARNING: upcase has been sealed] true false
Using Refinements to perform a safe, scope-limited extension.
refinements.rb
# Safe, scope-limited extension using Refinements.
module EvangelionExtension
refine String do
# Adds a rank prefix to a character name.
def with_rank(rank)
"#{rank} #{self}"
end
# Converts a line to log format.
def as_log(speaker)
"[#{speaker}] #{self}"
end
end
refine Integer do
# Displays the value as a sync rate.
def sync_rate
"Sync rate: #{self}%"
end
end
end
# Refinements are active only within the scope where using is called.
using EvangelionExtension
pilots = [
{ name: "Shinji Ikari", rank: "Pilot", sync: 141 },
{ name: "Rei Ayanami", rank: "First", sync: 88 },
{ name: "Asuka Langley Soryu", rank: "Second", sync: 402 },
{ name: "Misato Katsuragi", rank: "Major", sync: 0 },
{ name: "Kaworu Nagisa", rank: "Fifth", sync: 100 },
]
pilots.each do |pilot|
name_with_rank = pilot[:name].with_rank(pilot[:rank])
log_line = pilot[:sync].sync_rate.as_log(name_with_rank)
puts log_line
end
ruby refinements.rb [Pilot Shinji Ikari] Sync rate: 141% [First Rei Ayanami] Sync rate: 88% [Second Asuka Langley Soryu] Sync rate: 402% [Major Misato Katsuragi] Sync rate: 0% [Fifth Kaworu Nagisa] Sync rate: 100%
Confirming that Refinements do not affect other files.
refinements_scope.rb
# Refinements definition (inactive without using).
module EvangelionExtension
refine String do
def eva_shout
"[#{self}]"
end
end
end
# Without using, the extended method is not available.
begin
puts "Shinji Ikari".eva_shout
rescue NoMethodError => e
puts "Error: #{e.message}"
end
# Active only from the point where using is called.
using EvangelionExtension
puts "Shinji Ikari".eva_shout # [Shinji Ikari]
puts "Rei Ayanami".eva_shout # [Rei Ayanami]
puts "Asuka Langley Soryu".eva_shout # [Asuka Langley Soryu]
ruby refinements_scope.rb Error: undefined method 'eva_shout' for an instance of String [Shinji Ikari] [Rei Ayanami] [Asuka Langley Soryu]
Notes
Open class is one of Ruby's major features, allowing methods to be added after the fact to any existing class, including built-in classes. Because useful utility methods can be added to existing classes in an intuitive way, this technique is widely used in small scripts and inside frameworks (for example, the many extension methods provided by Rails' ActiveSupport).
On the other hand, changes made via open class have global effect. When multiple libraries try to extend the same method on the same class separately, the later definition overwrites the earlier one. In large projects and when coexisting with external libraries, this "monkey-patching" can cause unexpected bugs, so it warrants careful handling.
Refinements, introduced in Ruby 2.0, are a safer solution to this problem. Methods defined in a refine block take effect only within the file or module where using is called, containing the extension's impact to a limited scope. Refinements are commonly used when the flexibility of open class is needed without global side effects.
For the basics of class definition, see class / initialize / attr_accessor. For mixin modules, see module / include / extend.
Common Mistakes
Mistake 1: Accidentally overwriting an existing method and introducing a silent bug
Defining a method in open class with the same name as an existing method affects all instances of that class. Any behavior expected by the standard library or other code changes, which can cause unexpected bugs.
ng_overwrite.rb
# Example that accidentally overwrites String#include?.
class String
def include?(other)
false # always returns false
end
end
puts "Shinji Ikari".include?("Ikari") # false (should be true)
puts "Rei Ayanami".include?("Rei") # false (should be true)
ruby ng_overwrite.rb false false
Correct approach: check for existing methods with method_defined? before adding. If the name conflicts, consider a different name.
ok_check_before_add.rb
class String
# Check for existing methods before adding.
if method_defined?(:shout)
puts "shout is already defined. Skipping."
else
def shout
self + "!"
end
end
end
puts "Shinji Ikari".shout # Shinji Ikari!
puts String.method_defined?(:include?) # true (standard method is intact)
puts "Shinji Ikari".include?("Ikari") # true (behavior unchanged)
ruby ok_check_before_add.rb Shinji Ikari! true true
Mistake 2: Scope restriction when calling using inside a class
using can be called at the top level of a file or inside a module definition. When called inside a class definition, the Refinements are not applied to the method definitions within that class body (behavior as of Ruby 3.x).
ng_using_in_class.rb
module EvaExtension
refine String do
def eva_shout
"[#{self}]"
end
end
end
class Pilot
using EvaExtension # calling using inside a class
def shout(phrase)
phrase.eva_shout # Refinements may not be active in method definitions inside a class
end
end
pilot = Pilot.new
puts pilot.shout("I mustn't run away")
ruby ng_using_in_class.rb ng_using_in_class.rb:12:in 'Pilot#shout': undefined method 'eva_shout' for an instance of String (NoMethodError)
Correct approach: call using at the top level of the file.
ok_using_toplevel.rb
module EvaExtension
refine String do
def eva_shout
"[#{self}]"
end
end
end
# Call using at the file's top level.
using EvaExtension
class Pilot
def shout(phrase)
phrase.eva_shout
end
end
pilot = Pilot.new
puts pilot.shout("I mustn't run away") # [I mustn't run away]
puts pilot.shout("I can do anything") # [I can do anything]
ruby ok_using_toplevel.rb [I mustn't run away] [I can do anything]
If you find any errors or copyright issues, please contact us.