Introduction
Ruby lets you reopen any class at any time and add or change its methods. This lesson explores how open classes work, why they are fundamental to Ruby metaprogramming, and when the power they provide is worth the risk.
Key Concepts
- Open Class: A class whose definition can be reopened and modified after its initial creation.
- Monkey Patching: Modifying or overriding methods on an existing class, including core Ruby classes.
- Refinements: A safer, lexically-scoped alternative to monkey patching introduced in Ruby 2.0.
Real World Context
Imagine you are integrating a third-party gem that returns plain String values, but your app consistently needs a #to_slug helper. Rather than wrapping every call, you could reopen String and add #to_slug once. Rails does exactly this with ActiveSupport core extensions like "hello".pluralize.
Deep Dive
In Ruby, the class keyword does not just define a class — it opens the class for modification. If the class already exists, Ruby simply enters its body and executes the code inside.
The following example shows how you can add a new method to the built-in String class:
rubyclass String def shout upcase + "!" end end "hello".shout # => "HELLO!"
The shout method is now available on every String instance because Ruby reopened the existing String class rather than creating a new one.
You can also reopen your own classes to add methods incrementally:
ruby# First definition class User def name @name end end # Later, reopen and add method class User def email @email end end # User now has both methods!
Both name and email are available on User instances because the second class User block simply reopened the existing class.
Modifying existing classes — especially core classes — is called monkey patching. It is powerful but dangerous because every consumer of that class sees the change:
ruby# Dangerous: modifying core behavior class Array def first "surprise!" # Breaks everything! end end
Overriding Array#first globally means every piece of code in the process that calls first on an array will get the wrong result. This is why monkey patching should be used sparingly.
Common Pitfalls
- Overriding core methods silently — If you redefine a method that already exists (like
Array#first), Ruby will not warn you. The old implementation is simply replaced, which can break unrelated code. - Gem version conflicts — Two gems that monkey-patch the same method on the same class will conflict. The last one loaded wins, and the other silently disappears.
Best Practices
- Prefer Refinements — Use
refineandusingto scope your modifications to the file or module that needs them, avoiding global side effects. - Limit to domain-specific extensions — Only add methods when they represent a genuine concept in your domain (e.g.,
String#to_slugin a CMS), and document them clearly.
Summary
- Ruby classes can be reopened at any time to add or change methods.
- Monkey patching modifies classes globally and can introduce hard-to-trace bugs.
- Refinements provide a scoped alternative that limits the blast radius of class modifications.
Code Examples
class String
def to_slug
downcase.strip.gsub(/\s+/, '-').gsub(/[^a-z0-9\-]/, '')
end
end
"Hello World!".to_slug # => "hello-world"# Safe alternative using Refinements
module SlugSupport
refine String do
def to_slug
downcase.strip.gsub(/\s+/, '-').gsub(/[^a-z0-9\-]/, '')
end
end
end
using SlugSupport
"Hello World!".to_slug # => "hello-world"