Ruby Object Model Open Classes & Refinements

Ruby’s power lies in its open object model — where classes can be reopened and modified at runtime — making it incredibly flexible for metaprogramming and DSLs.

In this post, we’ll explore how Ruby’s open classes work under the hood, why they enable elegant code reuse, and where they can go wrong. We’ll also look at refinements — Ruby’s built-in way to scope monkey patches safely — and see how they help balance flexibility with maintainability.

By the end, you’ll understand why these features exist, and how to use them thoughtfully in real-world projects.

Open Classes

Ruby allows you to reopen any existing class or module at any time. This enables developers to add, remove, or redefine methods dynamically.

This behavior is commonly known as “monkey patching”.

Example of an open class:

class String
  def shout
    upcase + "!"
  end
end

puts "hello".shout # => "HELLO!"

With this, all String objects now have a shout method. However, because these changes are global, they may lead to unexpected side effects if multiple parts of an application make conflicting modifications.

Refinements

Refinements in Ruby offer a way to scope these class modifications locally, reducing the risks associated with open classes.

A refinement is defined in a module and activated with the `using` keyword. Only the code that activates the refinement will see the changes.

Refinement Example:

class C
  def foo
    "original foo"
  end
end

module M
  refine C do
    def foo
      "refined foo"
    end
  end
end

c = C.new
puts c.foo         # => "original foo"

using M
puts c.foo         # => "refined foo"

Key features of refinements:

Exploring Ruby Meta-level with ObjectSpace and method(:foo).owner

Example:

require 'objspace'
ObjectSpace.each_object(Class) { |cls| puts cls }

This prints the names of all existing classes.

Example:

"str".method(:upcase).owner # => String

This returns the class or module where the method was originally defined, useful for debugging the method lookup path.

Recap