Ruby is one of the few languages where classes are objects, capable of holding both instance behavior and class-level behavior. This flexibility comes from a powerful internal structure: the singleton class, also known as the eigenclass. Every Ruby object has one โ including classes themselves.
When developers write class << self, they are opening a special, hidden class that Ruby uses to store methods that belong to the class object, not its instances. This technique is the backbone of Ruby’s expressive meta-programming features and is used heavily in Rails, Sidekiq, ActiveRecord, RSpec, and nearly every major Ruby framework.
This article explains why Ruby has singleton classes, what they enable, and when you should use class << self instead of def self.method for defining class-level behavior.
In Ruby, writing:
class Payment; end
creates an object:
Payment.instance_of?(Class) # => true
Since Payment is an object, it can have:
- Its own methods
- Its own attributes
- Its own included modules
Just like any other object.
Ruby stores these class-specific methods in a special internal structure: the singleton class of Payment.
When you define a class method:
def self.process
end
Ruby is actually doing this under the hood:
- Open the singleton class of
Payment - Define
processinside it
So:
class << Payment
def process; end
end
and:
def Payment.process; end
and:
def self.process; end
All do the same thing.
But class << self unlocks far more power.
Each Ruby object has:
[ Object ] ---> [ Singleton Class ] ---> [ Its Class ]
For a class object like Payment:
[ Payment ] ---> [ Payment's Eigenclass ] ---> [ Class ]
Instance methods live in Payment.
Class methods live in Payment's eigenclass.
The eigenclass is where Ruby stores:
- Class methods
- Per-object overrides
- Class-specific attributes
- DSL behaviors
class << self
def load; end
def export; end
def sync; end
end
Cleaner than:
def self.load; end
def self.export; end
def self.sync; end
This is a huge advantage.
class << self
private
def connection_pool
@pool ||= ConnectionPool.new
end
end
Using def self.method cannot make the method private โ Ruby doesn’t allow it.
class << self
include CacheHelpers
end
This modifies class-level behavior, not instance behavior.
Rails uses this technique everywhere.
You must open the eigenclass:
class << self
def new(*args)
puts "Creating a new Payment!"
super
end
end
This cannot be done properly with def self.new.
class << self
attr_accessor :config
end
Usage:
Payment.config = { currency: "USD" }
This config belongs to the class itself.
Example from ActiveRecord:
class << self
def has_many(name)
# defines association
end
end
Or RSpec:
class << self
def describe(text, &block)
# builds DSL structure
end
end
When you write:
class Order < ApplicationRecord
has_many :line_items
end
Internally Rails does:
class Order
class << self
def has_many(name)
# logic here
end
end
end
This is how Rails builds its elegant DSL.
class << self
def before_save(method_name)
set_callback(:save, :before, method_name)
end
end
Again, these DSL methods live in the singleton class.
โ
Use def self.method_name when:
- Only defining 1โ2 methods
- Simpler readability is preferred
โ
Use class << self when:
- You have many class methods
- You require private class methods
- You need to include modules at class level
- You are building DSLs or metaprogramming-heavy components
- You need to override class-level behavior (
new,allocate)
Opening a class’s singleton class (class << self) is not just a stylistic choice โ it is a powerful meta-programming technique that lets you modify the behavior of the class object itself. Because Ruby treats classes as first-class objects, their singleton classes hold the key to defining class methods, private class-level utilities, DSLs, and dynamic meta-behavior.
Understanding how and why Ruby uses the eigenclass gives you deeper insight into the design of Rails, Sidekiq, ActiveRecord, and virtually all major Ruby libraries.
Itโs one of the most elegant aspects of Ruby’s object model โ and one of its most powerful once mastered.
Happy Ruby coding!