Skip to main content
โšก Calmops

Class Methods and Singleton Methods in Ruby

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

Comments