Overview
Singleton methods in Ruby are methods defined on individual objects rather than on classes. When you define a singleton method on an object, that method exists only for that specific object instance and does not affect other instances of the same class. Ruby implements singleton methods through a hidden class called the eigenclass (also known as the singleton class or metaclass).
The eigenclass sits between an object and its class in Ruby's method lookup chain. When Ruby encounters a singleton method definition, it creates this eigenclass if it doesn't exist and defines the method there. This mechanism enables Ruby's flexible object model where any object can have its own unique methods.
str = "hello"
def str.shout
self.upcase + "!"
end
str.shout # => "HELLO!"
"world".shout # NoMethodError: undefined method `shout' for "world":String
Class methods in Ruby are actually singleton methods defined on the class object itself. When you write def self.method_name
, you're creating a singleton method on the class object.
class User
def self.find_by_email(email)
# class method implementation
end
end
# Equivalent to:
class User
end
def User.find_by_email(email)
# same implementation
end
Ruby's extend
keyword adds singleton methods by copying methods from a module to the object's eigenclass. This differs from include
, which adds methods to the inheritance chain for all instances.
module Trackable
def track_event(event)
puts "Tracking: #{event}"
end
end
user = User.new
user.extend(Trackable)
user.track_event("login") # => "Tracking: login"
other_user = User.new
other_user.track_event("login") # NoMethodError
Basic Usage
The most direct way to create singleton methods uses the def object.method_name
syntax. This creates a method that exists only on the specified object.
account = BankAccount.new
def account.freeze_account
@frozen = true
puts "Account frozen"
end
account.freeze_account # => "Account frozen"
Class methods represent the most common singleton method pattern. Ruby provides multiple syntaxes for defining class methods, all creating singleton methods on the class object.
class Product
# Using def self.method_name
def self.featured
where(featured: true)
end
# Using class << self
class << self
def on_sale
where("discount > 0")
end
def by_category(category)
where(category: category)
end
end
end
# Using def ClassName.method_name outside class definition
def Product.trending
where("created_at > ?", 1.week.ago)
end
The class << object
syntax opens the eigenclass for method definitions. This syntax proves useful when defining multiple singleton methods or when you need access to the eigenclass itself.
logger = Logger.new
class << logger
def log_with_timestamp(message)
info("[#{Time.now}] #{message}")
end
def log_error_with_backtrace(error)
error(error.message)
error(error.backtrace.join("\n"))
end
end
Singleton methods interact with Ruby's method lookup chain. When calling a method on an object, Ruby first checks the object's eigenclass before proceeding up the inheritance hierarchy.
class Animal
def speak
"Some sound"
end
end
dog = Animal.new
def dog.speak
"Woof!"
end
dog.speak # => "Woof!" (singleton method)
cat = Animal.new
cat.speak # => "Some sound" (instance method)
# Method lookup order for dog:
# 1. dog's eigenclass (singleton methods)
# 2. Animal class
# 3. Object class
# 4. BasicObject class
Advanced Usage
Singleton methods enable sophisticated metaprogramming patterns through dynamic method definition and eigenclass manipulation. The define_singleton_method
method creates singleton methods programmatically.
class ConfigurableService
def self.define_endpoint(name, url, options = {})
define_singleton_method("fetch_#{name}") do |params = {}|
http_client = Net::HTTP.new(URI(url).host, URI(url).port)
request = Net::HTTP::Get.new(URI(url).path)
options.each do |key, value|
request[key.to_s] = value
end
params.each do |key, value|
request.set_form_data(key => value)
end
response = http_client.request(request)
JSON.parse(response.body)
end
end
end
ConfigurableService.define_endpoint(:users, 'https://api.example.com/users')
ConfigurableService.define_endpoint(:orders, 'https://api.example.com/orders',
'Authorization' => 'Bearer token123')
users = ConfigurableService.fetch_users(page: 1)
orders = ConfigurableService.fetch_orders(status: 'pending')
Module extension creates powerful composition patterns by adding singleton methods from modules. This technique builds object-specific behaviors without modifying class definitions.
module CacheableResource
def cached_find(id)
@cache ||= {}
@cache[id] ||= find(id)
end
def invalidate_cache(id = nil)
if id
@cache&.delete(id)
else
@cache = {}
end
end
def cache_stats
{
size: @cache&.size || 0,
keys: @cache&.keys || []
}
end
end
class User < ActiveRecord::Base
end
class Product < ActiveRecord::Base
end
User.extend(CacheableResource)
user = User.cached_find(123) # Cached lookup
User.cache_stats # => {size: 1, keys: [123]}
# Product class doesn't have caching behavior
Product.find(456) # Standard database lookup
Eigenclass access enables advanced metaprogramming by providing direct manipulation of the singleton class. This pattern creates complex object hierarchies and method delegation systems.
class DynamicProxy
def initialize(target)
@target = target
eigenclass = class << self; self; end
@target.class.instance_methods(false).each do |method_name|
eigenclass.define_method(method_name) do |*args, &block|
puts "Proxying #{method_name} with args: #{args}"
result = @target.send(method_name, *args, &block)
puts "Result: #{result}"
result
end
end
# Add proxy-specific methods
eigenclass.define_method(:target) { @target }
eigenclass.define_method(:proxy_stats) do
{
target_class: @target.class,
proxied_methods: @target.class.instance_methods(false),
proxy_id: object_id
}
end
end
end
class Calculator
def add(a, b)
a + b
end
def multiply(a, b)
a * b
end
end
calc = Calculator.new
proxy = DynamicProxy.new(calc)
proxy.add(5, 3) # Outputs: "Proxying add with args: [5, 3]"
# "Result: 8"
# => 8
proxy.multiply(4, 7) # Outputs: "Proxying multiply with args: [4, 7]"
# "Result: 28"
# => 28
proxy.proxy_stats # => {target_class: Calculator,
# proxied_methods: [:add, :multiply],
# proxy_id: 70368527277780}
Singleton method chaining creates fluent interfaces by returning objects with dynamically added methods. This pattern builds expressive APIs for configuration and data processing.
class QueryBuilder
def initialize(model_class)
@model_class = model_class
@conditions = []
@ordering = []
@limit_value = nil
end
def where(conditions)
new_builder = clone
new_builder.instance_variable_set(:@conditions, @conditions + [conditions])
# Add dynamic methods based on conditions
if conditions.is_a?(Hash)
conditions.keys.each do |key|
new_builder.define_singleton_method("by_#{key}") do |value|
where(key => value)
end
end
end
new_builder
end
def order(column)
new_builder = clone
new_builder.instance_variable_set(:@ordering, @ordering + [column])
new_builder
end
def limit(count)
new_builder = clone
new_builder.instance_variable_set(:@limit_value, count)
new_builder
end
def to_sql
sql = "SELECT * FROM #{@model_class.table_name}"
unless @conditions.empty?
conditions_sql = @conditions.map do |cond|
if cond.is_a?(Hash)
cond.map { |k, v| "#{k} = '#{v}'" }.join(' AND ')
else
cond
end
end.join(' AND ')
sql += " WHERE #{conditions_sql}"
end
unless @ordering.empty?
sql += " ORDER BY #{@ordering.join(', ')}"
end
sql += " LIMIT #{@limit_value}" if @limit_value
sql
end
end
# Usage creates dynamic methods based on query conditions
query = QueryBuilder.new(User)
.where(status: 'active', role: 'admin')
.order('created_at DESC')
.limit(10)
# Dynamic methods created from where conditions
admin_query = query.by_status('active').by_role('admin')
admin_query.to_sql
# => "SELECT * FROM users WHERE status = 'active' AND role = 'admin' AND status = 'active' AND role = 'admin' ORDER BY created_at DESC LIMIT 10"
Common Pitfalls
Singleton method inheritance behaves differently than regular method inheritance. When you create a subclass, singleton methods from the parent class don't automatically transfer to the child class object.
class User
def self.admin_count
where(role: 'admin').count
end
end
class AdminUser < User
end
User.admin_count # Works
AdminUser.admin_count # NoMethodError: undefined method `admin_count' for AdminUser:Class
# Workaround using inherited hook
class User
def self.admin_count
where(role: 'admin').count
end
def self.inherited(subclass)
def subclass.admin_count
where(role: 'admin').count
end
end
end
class AdminUser < User
end
AdminUser.admin_count # Now works
Eigenclass visibility rules create confusion when defining private singleton methods. The private
keyword affects different scopes depending on the definition context.
class SecretService
private
def self.classified_method
"top secret"
end
class << self
def another_classified_method
"also top secret"
end
private
def truly_private_method
"really private"
end
end
end
# These behave differently:
SecretService.classified_method # Works - not actually private
SecretService.another_classified_method # Works - not private either
SecretService.truly_private_method # NoMethodError - actually private
# Correct way to define private class methods:
class SecretService
class << self
def public_method
"accessible"
end
private
def private_method
"truly private"
end
end
end
Variable scope inside singleton methods leads to unexpected behavior when accessing instance variables and class variables. Singleton methods defined on classes cannot directly access instance variables of the class.
class Counter
@class_count = 0
@@total_count = 0
def initialize
@instance_count = 0
end
def self.increment_class_count
@class_count += 1 # This works - class instance variable
end
def self.increment_total_count
@@total_count += 1 # This works - class variable
end
def self.broken_instance_access
@instance_count += 1 # This creates a NEW instance variable on the class!
end
def increment_instance_count
@instance_count += 1
end
def self.class_count
@class_count
end
def instance_count
@instance_count
end
end
counter = Counter.new
Counter.increment_class_count
Counter.broken_instance_access
counter.increment_instance_count
Counter.class_count # => 1
counter.instance_count # => 1
# Check what @instance_count is on the class:
class << Counter
attr_reader :instance_count
end
Counter.instance_count # => 1 (different from the object's @instance_count!)
Method redefinition on singleton methods creates memory leaks in long-running applications. Each redefinition creates a new method object without cleaning up the previous one.
class DynamicHandler
@handlers = {}
def self.register_handler(name, &block)
@handlers[name] = block
# This creates a memory leak if called repeatedly
define_singleton_method("handle_#{name}") do |*args|
@handlers[name].call(*args)
end
end
def self.handlers_count
@handlers.size
end
end
# Memory leak scenario:
1000.times do |i|
DynamicHandler.register_handler("temp_handler") { |x| x * 2 }
end
# Better approach - check if method exists first
class DynamicHandler
def self.register_handler(name, &block)
@handlers[name] = block
method_name = "handle_#{name}"
# Only define if it doesn't exist
unless respond_to?(method_name)
define_singleton_method(method_name) do |*args|
@handlers[name].call(*args)
end
end
end
end
Module extension timing affects method availability. Methods added to a module after extending an object don't automatically become available on that object.
module Trackable
def log_event(event)
puts "Logged: #{event}"
end
end
user = User.new
user.extend(Trackable)
user.log_event("signup") # => "Logged: signup"
# Add method to module after extending
module Trackable
def log_error(error)
puts "Error: #{error}"
end
end
user.log_error("failed") # NoMethodError: undefined method `log_error'
# The new method is only available to newly extended objects
new_user = User.new
new_user.extend(Trackable)
new_user.log_error("failed") # => "Error: failed"
# Workaround - re-extend the object
user.extend(Trackable)
user.log_error("failed") # => "Error: failed"
Reference
Singleton Method Definition Syntaxes
Syntax | Context | Usage | Example |
---|---|---|---|
def object.method |
Outside object definition | Define method on specific object | def user.activate; end |
def self.method |
Inside class/module definition | Define class method | def self.find_all; end |
class << object |
Any context | Open eigenclass for multiple definitions | class << user; def activate; end; end |
define_singleton_method |
Programmatic definition | Dynamic singleton method creation | define_singleton_method(:activate) { } |
Core Methods for Singleton Method Management
Method | Parameters | Returns | Description |
---|---|---|---|
singleton_methods(all=true) |
all (Boolean) |
Array<Symbol> |
Lists singleton methods on object |
singleton_method(name) |
name (Symbol/String) |
Method |
Returns singleton method object |
define_singleton_method(name, &block) |
name (Symbol/String), block |
Method |
Defines singleton method programmatically |
singleton_class |
None | Class |
Returns eigenclass of object |
extend(module, ...) |
One or more modules | self |
Adds module methods as singleton methods |
Method Introspection
Method | Parameters | Returns | Description |
---|---|---|---|
respond_to?(method, include_private=false) |
method (Symbol/String), include_private (Boolean) |
Boolean |
Checks if object responds to method |
method_defined?(method) |
method (Symbol/String) |
Boolean |
Checks if method is defined in class |
singleton_method_defined?(method) |
method (Symbol/String) |
Boolean |
Checks if singleton method is defined |
methods(regular=true) |
regular (Boolean) |
Array<Symbol> |
Lists all methods available on object |
Eigenclass Operations
Method | Parameters | Returns | Description |
---|---|---|---|
class << object; self; end |
None | Class |
Returns eigenclass directly |
eigenclass.instance_methods(false) |
false |
Array<Symbol> |
Lists methods defined in eigenclass only |
eigenclass.ancestors |
None | Array<Class> |
Shows eigenclass inheritance chain |
Module Extension Patterns
Pattern | Usage | Effect | Example |
---|---|---|---|
object.extend(Module) |
Instance extension | Adds module methods as singleton methods to object | user.extend(Trackable) |
Class.extend(Module) |
Class extension | Adds module methods as class methods | User.extend(Findable) |
extend Module |
Inside class definition | Same as self.extend(Module) |
Inside User class: extend Findable |
Visibility Modifiers for Singleton Methods
Context | Modifier Placement | Effect | Example |
---|---|---|---|
class << self block |
private after method definitions |
Makes singleton methods private | class << self; def method; end; private :method; end |
Direct class method definition | private_class_method |
Makes class method private | private_class_method :method_name |
Instance singleton methods | Standard private |
Makes singleton method private | class << obj; private; def method; end; end |
Method Lookup Chain with Singleton Methods
Object Type | Lookup Order | Description |
---|---|---|
Regular instance | eigenclass → class → superclass → ... → BasicObject | Singleton methods checked first |
Class object | class eigenclass → Class → Module → Object → BasicObject | Class methods are singleton methods on class object |
Module object | module eigenclass → Module → Object → BasicObject | Module methods use same pattern |
Common Eigenclass Patterns
Pattern | Code Structure | Use Case |
---|---|---|
Class methods | class << self; end inside class |
Multiple related class methods |
Instance enhancement | class << instance; end |
Adding behavior to specific objects |
Module self-extension | module M; extend self; end |
Module methods callable as M.method |
Delegation | class << @target; self; end |
Proxy or decorator patterns |
Error Types Related to Singleton Methods
Error | Cause | Solution |
---|---|---|
NoMethodError |
Singleton method called on wrong object | Check object has the singleton method |
NameError |
Undefined method in eigenclass context | Define method in correct scope |
ArgumentError |
Wrong parameters for define_singleton_method |
Check block arity matches expected parameters |
TypeError |
Attempt to define singleton method on immutable object | Use different object or approach |