CrackedRuby logo

CrackedRuby

Eigenclass Concept

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