Overview
Ruby creates an eigenclass (singleton class) for every object to store methods specific to that individual object instance. The eigenclass sits between an object and its regular class in the method lookup chain, allowing Ruby to define methods on single objects without affecting other instances of the same class.
When Ruby encounters a singleton method definition, it creates or accesses the object's eigenclass and defines the method there. This mechanism supports Ruby's flexible object model where any object can have unique behavior.
obj = Object.new
def obj.custom_method
"This method exists only on this object"
end
obj.custom_method
# => "This method exists only on this object"
Object.new.custom_method
# NoMethodError: undefined method `custom_method'
The eigenclass becomes part of the method lookup chain. Ruby searches the object's eigenclass first, then the object's class, then ancestor classes and included modules.
class MyClass
def instance_method
"from class"
end
end
obj = MyClass.new
def obj.instance_method
"from eigenclass"
end
obj.instance_method
# => "from eigenclass"
Ruby provides several ways to access and manipulate eigenclasses. The singleton_class
method returns an object's eigenclass, while class << obj
opens the eigenclass for method definitions.
str = "hello"
eigenclass = str.singleton_class
eigenclass.class
# => Class
class << str
def reverse_upcase
reverse.upcase
end
end
str.reverse_upcase
# => "OLLEH"
Class methods in Ruby are singleton methods defined on class objects. When defining class methods, Ruby creates eigenclasses for the class objects themselves.
Basic Usage
Define singleton methods directly on objects using the def object.method_name
syntax. Ruby creates the eigenclass automatically and stores the method definition there.
person = Object.new
def person.name
@name ||= "Anonymous"
end
def person.name=(value)
@name = value
end
person.name = "Alice"
person.name
# => "Alice"
Access an object's eigenclass using the singleton_class
method. This returns the actual eigenclass object where singleton methods are stored.
array = [1, 2, 3]
def array.sum_doubled
sum * 2
end
eigenclass = array.singleton_class
eigenclass.instance_methods(false)
# => [:sum_doubled]
Use class << object
syntax to open the eigenclass context and define multiple methods. This approach groups related singleton methods together.
calculator = Object.new
class << calculator
def add(a, b)
a + b
end
def multiply(a, b)
a * b
end
def chain_operations(*operations)
operations.inject { |result, op| send(op.first, result, op.last) }
end
end
calculator.add(5, 3)
# => 8
calculator.chain_operations([:add, 10], [:multiply, 2])
# => 28
Define class methods using eigenclass syntax. Class methods are singleton methods on class objects, following the same eigenclass mechanism.
class MathUtils
class << self
def pi
3.14159265359
end
def circle_area(radius)
pi * radius * radius
end
end
end
MathUtils.circle_area(5)
# => 78.53981633975
Ruby modules can extend objects by adding their methods to the object's eigenclass. The extend
method copies module methods into the eigenclass.
module Greetings
def hello
"Hello from #{self.class}"
end
def goodbye
"Goodbye from #{self.class}"
end
end
obj = Object.new
obj.extend(Greetings)
obj.hello
# => "Hello from Object"
obj.singleton_class.included_modules
# => [Greetings]
Check if an object has singleton methods using various inspection methods. Ruby provides several ways to examine eigenclass contents.
str = "example"
def str.custom_length
length * 2
end
str.singleton_methods
# => [:custom_length]
str.singleton_class.instance_methods(false)
# => [:custom_length]
str.respond_to?(:custom_length)
# => true
Advanced Usage
Eigenclasses form inheritance chains that mirror the object inheritance hierarchy. When a class inherits from another class, the eigenclass of the child class inherits from the eigenclass of the parent class.
class Parent
def self.family_name
"Smith"
end
end
class Child < Parent
def self.generation
"Third"
end
end
Child.family_name
# => "Smith"
Child.singleton_class.superclass == Parent.singleton_class
# => true
Access eigenclass inheritance chains programmatically to understand method resolution. This technique helps debug complex metaprogramming scenarios.
class Base
def self.base_method
"from base"
end
end
class Derived < Base
def self.derived_method
"from derived"
end
end
# Walk the eigenclass chain
current = Derived.singleton_class
while current
puts "#{current} methods: #{current.instance_methods(false)}"
current = current.superclass
break if current == Class
end
Define eigenclass methods conditionally based on runtime characteristics. This pattern enables dynamic behavior customization per object.
class DatabaseConnection
def initialize(config)
@config = config
setup_connection_methods
end
private
def setup_connection_methods
eigenclass = singleton_class
if @config[:read_only]
eigenclass.define_method(:execute) do |sql|
raise "Read-only connection" if sql.match?(/INSERT|UPDATE|DELETE/i)
perform_query(sql)
end
else
eigenclass.define_method(:execute) do |sql|
perform_query(sql)
end
end
end
def perform_query(sql)
"Executing: #{sql}"
end
end
readonly_db = DatabaseConnection.new(read_only: true)
regular_db = DatabaseConnection.new(read_only: false)
regular_db.execute("INSERT INTO users VALUES (1, 'Alice')")
# => "Executing: INSERT INTO users VALUES (1, 'Alice')"
readonly_db.execute("INSERT INTO users VALUES (2, 'Bob')")
# RuntimeError: Read-only connection
Create eigenclass method factories that generate specialized behavior. This approach combines eigenclasses with metaprogramming for flexible object customization.
module Cacheable
def self.extended(obj)
cache = {}
obj.singleton_class.define_method(:cached_method) do |method_name, *args, &block|
key = [method_name, args].hash
cache[key] ||= if block_given?
block.call(*args)
else
send(method_name, *args)
end
end
obj.singleton_class.define_method(:clear_cache) do
cache.clear
end
obj.singleton_class.define_method(:cache_stats) do
{ size: cache.size, keys: cache.keys }
end
end
end
expensive_calculator = Object.new
expensive_calculator.extend(Cacheable)
expensive_calculator.singleton_class.define_method(:fibonacci) do |n|
return n if n <= 1
fibonacci(n - 1) + fibonacci(n - 2)
end
# Use caching
result = expensive_calculator.cached_method(:fibonacci, 35)
expensive_calculator.cache_stats
# => { size: 1, keys: [some_hash_key] }
Implement eigenclass-based decorators that modify object behavior without changing the original class structure.
class Logger
def self.decorate(obj, prefix: obj.class.name)
original_methods = obj.class.instance_methods(false)
original_methods.each do |method_name|
original_method = obj.method(method_name)
obj.singleton_class.define_method(method_name) do |*args, &block|
puts "[#{prefix}] Calling #{method_name} with #{args.inspect}"
result = original_method.call(*args, &block)
puts "[#{prefix}] #{method_name} returned #{result.inspect}"
result
end
end
end
end
class Calculator
def add(a, b)
a + b
end
def multiply(a, b)
a * b
end
end
calc = Calculator.new
Logger.decorate(calc, prefix: "MATH")
calc.add(2, 3)
# [MATH] Calling add with [2, 3]
# [MATH] add returned 5
# => 5
Common Pitfalls
Eigenclass method definitions can override class methods unexpectedly. When defining singleton methods on objects that already have class methods with the same name, the eigenclass method takes precedence.
class TimeStamped
def self.current_time
Time.now.strftime("%Y-%m-%d")
end
end
# Later in code, someone adds a singleton method
def TimeStamped.current_time
"overridden!"
end
TimeStamped.current_time
# => "overridden!" (not the class method)
# The original method still exists but is hidden
TimeStamped.singleton_class.superclass.instance_method(:current_time)
# => #<UnboundMethod: Class#current_time>
Variable scoping in eigenclass contexts differs from regular method definitions. Instance variables accessed within eigenclass methods belong to the object whose eigenclass contains the method.
class Container
def initialize
@value = "instance variable"
end
def add_singleton_method
@eigenclass_var = "eigenclass context"
singleton_class.define_method(:show_variables) do
# @value belongs to the Container instance
# @eigenclass_var belongs to the Container instance too
[@value, @eigenclass_var]
end
end
end
container = Container.new
container.add_singleton_method
container.show_variables
# => ["instance variable", "eigenclass context"]
# Both variables belong to the same object
container.instance_variables
# => [:@value, :@eigenclass_var]
Eigenclass inheritance can create confusing method resolution scenarios. Understanding the complete lookup chain prevents unexpected method calls.
class Parent
def self.identify
"parent class method"
end
end
class Child < Parent
end
# Add singleton method to Parent's eigenclass
def Parent.identify
"parent singleton method"
end
# Child inherits the singleton method, not the class method
Child.identify
# => "parent singleton method"
# The original class method is still accessible
Parent.singleton_class.superclass.instance_method(:identify).bind(Parent).call
# => "parent class method"
Memory leaks can occur when eigenclasses hold references to large objects. Each eigenclass maintains references to its methods and any captured variables.
class DataProcessor
def initialize(large_dataset)
@dataset = large_dataset
end
def add_custom_filter(name, &block)
# The block captures self, which holds @dataset
singleton_class.define_method("filter_#{name}") do |data|
block.call(data)
end
end
end
processor = DataProcessor.new(Array.new(1_000_000, "data"))
processor.add_custom_filter("even") { |data| data.select.with_index { |_, i| i.even? } }
# processor.singleton_class holds reference to the large dataset
# Even if processor goes out of scope, the eigenclass might retain it
Method removal from eigenclasses requires specific techniques. Standard remove_method
calls may not work as expected with singleton methods.
obj = Object.new
def obj.temp_method
"temporary"
end
obj.respond_to?(:temp_method)
# => true
# Wrong way - this affects the class, not the eigenclass
# Object.remove_method(:temp_method) # Would raise error
# Correct way - remove from eigenclass
obj.singleton_class.remove_method(:temp_method)
obj.respond_to?(:temp_method)
# => false
Eigenclass inspection can be misleading when dealing with modules and inheritance. The singleton_class
method always returns a class, even when methods come from extended modules.
module Trackable
def track_access
@access_count = (@access_count || 0) + 1
end
end
obj = Object.new
obj.extend(Trackable)
# Eigenclass exists but shows as a class
obj.singleton_class.class
# => Class
# Module methods appear as eigenclass methods
obj.singleton_methods
# => [:track_access]
# But the module relationship is preserved
obj.singleton_class.included_modules
# => [Trackable]
# Direct method definition vs module extension have same result
def obj.direct_method; end
obj.singleton_methods.sort
# => [:direct_method, :track_access]
Reference
Core Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#singleton_class |
None | Class |
Returns the eigenclass of the object |
#singleton_methods(all=true) |
all (Boolean) |
Array<Symbol> |
Lists singleton methods defined on the object |
#extend(module, ...) |
module (Module) |
self |
Adds module methods to object's eigenclass |
Class#define_method(name, method) |
name (Symbol), method (Method/Proc) |
Symbol |
Defines instance method in eigenclass |
Class#remove_method(name) |
name (Symbol) |
self |
Removes method from eigenclass |
Class#undef_method(name) |
name (Symbol) |
self |
Undefines method in eigenclass |
Inspection Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#respond_to?(method, include_all=false) |
method (Symbol), include_all (Boolean) |
Boolean |
Checks if object responds to method including eigenclass methods |
Class#instance_methods(include_super=true) |
include_super (Boolean) |
Array<Symbol> |
Returns eigenclass instance methods |
Class#method_defined?(method) |
method (Symbol) |
Boolean |
Checks if method is defined in eigenclass |
Class#included_modules |
None | Array<Module> |
Lists modules extended into eigenclass |
Eigenclass Access Patterns
# Direct eigenclass access
object.singleton_class
# Eigenclass context opening
class << object
# method definitions here
end
# Module extension (adds to eigenclass)
object.extend(SomeModule)
# Method definition with eigenclass
def object.method_name
# method body
end
Inheritance Chain Structure
Object Instance
↓
Object's Eigenclass
↓
Object's Class
↓
Parent Classes...
↓
BasicObject
Class Method Eigenclass Chain
MyClass (class object)
↓
MyClass's Eigenclass
↓
Superclass's Eigenclass
↓
Class
↓
Module
↓
Object
↓
BasicObject
Common Eigenclass Patterns
Pattern | Syntax | Use Case |
---|---|---|
Singleton method | def obj.method |
Single object customization |
Class method | def self.method or class << self |
Class-level functionality |
Module extension | obj.extend(Module) |
Adding module methods to specific objects |
Dynamic definition | singleton_class.define_method |
Runtime method creation |
Conditional methods | if condition; def obj.method |
Context-dependent behavior |
Eigenclass Inspection Commands
# Check eigenclass existence
obj.singleton_class != obj.class
# => true (eigenclass exists)
# List all singleton methods
obj.singleton_methods(true)
# Check eigenclass inheritance
obj.singleton_class.superclass
# View eigenclass method definitions
obj.singleton_class.instance_methods(false)
# Check for specific eigenclass method
obj.singleton_class.method_defined?(:method_name)
Memory and Performance Considerations
Aspect | Impact | Mitigation |
---|---|---|
Memory overhead | Each object with singleton methods gets eigenclass | Use sparingly for frequently created objects |
Method lookup cost | Additional class in lookup chain | Minimal impact in practice |
Reference retention | Eigenclasses hold method references | Remove unneeded singleton methods |
Inheritance complexity | Deep eigenclass chains | Document eigenclass usage patterns |