Introduction
In Ruby, a class method is a method called on the class itself rather than on an instance. Under the hood, class methods are actually singleton methods defined on the class object. Understanding this relationship unlocks Ruby’s full object model.
Three Ways to Define Class Methods
Way 1: def self.method_name
The most common and idiomatic approach:
class Foo
def self.bar
puts "class method"
end
end
Foo.bar # => "class method"
Way 2: class « self Block
Opens the singleton class of self (the class object). Useful for defining multiple class methods at once:
class Foo
class << self
def bar
puts "class method"
end
def baz
puts "another class method"
end
# Can also define attr_accessor for class-level attributes
attr_accessor :config
end
end
Foo.bar # => "class method"
Foo.baz # => "another class method"
Foo.config = { timeout: 30 }
puts Foo.config.inspect # => {:timeout=>30}
Way 3: def ClassName.method_name
Defines a singleton method directly on the class object. Less common but valid:
class Foo; end
def Foo.bar
puts "class method"
end
Foo.bar # => "class method"
All three are equivalent โ they all define a method on Foo’s singleton class.
What Are Singleton Methods?
Every Ruby object has a hidden singleton class (also called eigenclass or metaclass) that can hold methods unique to that specific object. Class methods are just singleton methods on the class object:
# Singleton method on a regular object
dog = Object.new
def dog.speak
"Woof!"
end
dog.speak # => "Woof!"
Object.new.speak # => NoMethodError
# Class method = singleton method on the class object
class Dog
def self.species
"Canis lupus familiaris"
end
end
# These are equivalent:
Dog.singleton_methods # => [:species]
Dog.methods(false) # => [:species]
Inheritance of Class Methods
Class methods are inherited by subclasses:
class Animal
def self.kingdom
"Animalia"
end
def self.describe
"I am #{name}, kingdom: #{kingdom}"
end
end
class Dog < Animal
def self.species
"Canis lupus familiaris"
end
end
puts Dog.kingdom # => "Animalia" (inherited)
puts Dog.describe # => "I am Dog, kingdom: Animalia"
puts Dog.species # => "Canis lupus familiaris" (own method)
puts Dog.ancestors
# => [Dog, Animal, Object, Kernel, BasicObject]
Class Methods vs Instance Methods
class BankAccount
@@interest_rate = 0.05
# Class method โ called on BankAccount
def self.interest_rate
@@interest_rate
end
def self.interest_rate=(rate)
@@interest_rate = rate
end
# Instance method โ called on an account object
def initialize(balance)
@balance = balance
end
def balance
@balance
end
def apply_interest
@balance += @balance * self.class.interest_rate
end
end
# Class method calls
puts BankAccount.interest_rate # => 0.05
BankAccount.interest_rate = 0.06
# Instance method calls
account = BankAccount.new(1000)
account.apply_interest
puts account.balance # => 1060.0
Factory Methods Pattern
Class methods are ideal for factory patterns โ alternative constructors:
class Color
attr_reader :r, :g, :b
def initialize(r, g, b)
@r, @g, @b = r, g, b
end
# Factory methods as alternative constructors
def self.from_hex(hex)
hex = hex.delete('#')
r = hex[0..1].to_i(16)
g = hex[2..3].to_i(16)
b = hex[4..5].to_i(16)
new(r, g, b)
end
def self.from_hsl(h, s, l)
# HSL to RGB conversion
# ... (simplified)
new(0, 0, 0)
end
def self.red; new(255, 0, 0); end
def self.green; new(0, 255, 0); end
def self.blue; new(0, 0, 255); end
def self.white; new(255, 255, 255); end
def self.black; new(0, 0, 0); end
def to_hex
"#%02x%02x%02x" % [@r, @g, @b]
end
def to_s
"rgb(#{@r}, #{#{@g}, #{@b})"
end
end
red = Color.red
blue = Color.from_hex("#0000ff")
puts red.to_hex # => "#ff0000"
puts blue.to_hex # => "#0000ff"
Class Methods for Configuration (DSL Pattern)
Rails and many gems use class methods to build DSLs:
class Validator
@rules = []
class << self
def rules; @rules; end
def validates(field, **options)
@rules << { field: field, options: options }
end
def inherited(subclass)
subclass.instance_variable_set(:@rules, [])
end
end
def valid?
self.class.rules.all? do |rule|
value = send(rule[:field])
if rule[:options][:presence]
!value.nil? && !value.to_s.strip.empty?
else
true
end
end
end
end
class User < Validator
attr_accessor :name, :email
validates :name, presence: true
validates :email, presence: true
def initialize(name, email)
@name = name
@email = email
end
end
u1 = User.new("Alice", "[email protected]")
u2 = User.new("", "[email protected]")
puts u1.valid? # => true
puts u2.valid? # => false
Mixin Class Methods with extend
When you extend a module, its methods become class methods:
module Findable
def find(id)
puts "SELECT * FROM #{table_name} WHERE id = #{id}"
end
def all
puts "SELECT * FROM #{table_name}"
end
def table_name
name.downcase + "s"
end
end
class User
extend Findable
end
class Product
extend Findable
end
User.find(42) # => SELECT * FROM users WHERE id = 42
Product.all # => SELECT * FROM products
Inspecting Class Methods
class MyClass
def self.foo; end
def self.bar; end
def instance_method; end
end
# Class methods defined directly on MyClass
MyClass.singleton_methods(false) # => [:foo, :bar]
MyClass.methods(false) # => [:foo, :bar]
# All class methods including inherited
MyClass.methods.grep(/^[a-z]/).first(5)
# => [:foo, :bar, :new, :allocate, :superclass, ...]
Summary
| Syntax | What it does |
|---|---|
def self.method |
Defines class method (most common) |
class << self; def method |
Opens singleton class, defines class method |
def ClassName.method |
Defines singleton method on class (less common) |
extend ModuleName |
Adds module methods as class methods |
Class methods are singleton methods on the class object. They’re inherited by subclasses, making them ideal for factory methods, configuration DSLs, and shared utilities.
Resources
- Ruby Docs: Singleton Classes
- Rails Tips: Class and Instance Methods
- Metaprogramming Ruby โ Paolo Perrotta
Comments