Overview
Class variables in Ruby store data shared across all instances of a class and its subclasses, using the @@variable
syntax. Unlike instance variables that belong to specific objects, class variables maintain state at the class level and propagate through inheritance chains. Ruby implements class variables as shared storage that all instances and subclasses can access and modify.
Class methods define behavior that operates on the class itself rather than individual instances. Ruby provides multiple ways to define class methods: using self.method_name
, the class << self
syntax, or defining methods directly on the class object. Class methods cannot access instance variables directly but can work with class variables and other class-level data.
Ruby's implementation ensures class variables remain synchronized across inheritance hierarchies. When a subclass modifies a class variable, the change affects the parent class and all other subclasses sharing that variable. This behavior differs from constants, which Ruby handles through lookup chains, and class instance variables, which remain isolated per class.
class Database
@@connection_count = 0
def self.connect
@@connection_count += 1
puts "Connections: #{@@connection_count}"
end
end
class UserDatabase < Database
end
Database.connect # => Connections: 1
UserDatabase.connect # => Connections: 2
Database.connect # => Connections: 3
Class variables support complex initialization patterns and can store any Ruby object. The variable becomes available immediately when Ruby encounters the declaration, making them suitable for configuration data, shared resources, and cross-instance coordination.
class Logger
@@loggers = {}
@@default_level = :info
def self.create(name, level = @@default_level)
@@loggers[name] ||= new(level)
end
def self.find(name)
@@loggers[name]
end
end
Basic Usage
Class variables require declaration before use, typically at the class definition's beginning. Ruby raises a NameError
if code attempts to access an undefined class variable. The declaration establishes the variable for the entire class hierarchy, making it accessible from class methods, instance methods, and subclasses.
class Counter
@@count = 0
def initialize
@@count += 1
end
def self.total
@@count
end
def current_count
@@count
end
end
Counter.new
Counter.new
puts Counter.total # => 2
puts Counter.new.current_count # => 3
Class methods define using self.method_name
within the class body or by reopening the class's singleton class with class << self
. Both approaches create methods callable on the class object itself. Class methods commonly handle factory operations, configuration management, and class-wide state manipulation.
class User
@@users = []
def self.create(name, email)
user = new(name, email)
@@users << user
user
end
def self.find_by_email(email)
@@users.find { |user| user.email == email }
end
def self.count
@@users.length
end
attr_reader :name, :email
def initialize(name, email)
@name, @email = name, email
end
end
john = User.create("John", "john@example.com")
jane = User.create("Jane", "jane@example.com")
puts User.count # => 2
puts User.find_by_email("john@example.com").name # => John
Inheritance creates shared class variable namespaces between parent and child classes. Modifying a class variable in any class affects all classes in the hierarchy. This behavior enables coordination between related classes but requires careful management to avoid unexpected state changes.
class Vehicle
@@total_vehicles = 0
def initialize
@@total_vehicles += 1
end
def self.count
@@total_vehicles
end
end
class Car < Vehicle
end
class Truck < Vehicle
end
Car.new
Truck.new
puts Vehicle.count # => 2
puts Car.count # => 2
puts Truck.count # => 2
Class variables work within modules and can be included in classes through mixins. The including class gains access to the module's class variables, creating additional sharing opportunities across unrelated class hierarchies.
module Trackable
@@tracked_objects = []
def self.included(base)
base.extend(ClassMethods)
end
def track
@@tracked_objects << self
end
module ClassMethods
def tracked_count
@@tracked_objects.length
end
def tracked_objects
@@tracked_objects
end
end
end
class Document
include Trackable
def initialize(title)
@title = title
track
end
end
class Photo
include Trackable
def initialize(filename)
@filename = filename
track
end
end
Document.new("Report")
Photo.new("vacation.jpg")
puts Document.tracked_count # => 2
puts Photo.tracked_count # => 2
Advanced Usage
Class variables support complex initialization patterns through conditional assignment and lazy loading. Ruby evaluates class variable assignments when the class definition loads, but conditional logic can defer initialization until actual usage occurs.
class ConfigManager
@@config = nil
@@loaded = false
def self.config
load_config unless @@loaded
@@config
end
def self.set(key, value)
load_config unless @@loaded
@@config[key] = value
end
def self.get(key)
load_config unless @@loaded
@@config[key]
end
private
def self.load_config
@@config = {
database_url: ENV['DATABASE_URL'] || 'sqlite://memory',
log_level: ENV['LOG_LEVEL'] || 'info',
cache_size: ENV['CACHE_SIZE']&.to_i || 100
}
@@loaded = true
end
end
# Configuration loads on first access
puts ConfigManager.get(:log_level) # => "info"
ConfigManager.set(:timeout, 30)
puts ConfigManager.config[:timeout] # => 30
Metaprogramming with class variables enables dynamic behavior creation and runtime class modification. Class variables can store method definitions, configuration blocks, and other executable code for later evaluation.
class EventHandler
@@handlers = Hash.new { |h, k| h[k] = [] }
def self.on(event, &block)
@@handlers[event] << block
end
def self.trigger(event, *args)
@@handlers[event].each { |handler| handler.call(*args) }
end
def self.handler_count(event = nil)
event ? @@handlers[event].length : @@handlers.values.sum(&:length)
end
# Dynamic method creation based on registered events
def self.method_missing(method_name, *args, &block)
if method_name.to_s.start_with?('on_')
event = method_name.to_s.sub(/^on_/, '').to_sym
on(event, &block)
else
super
end
end
def self.respond_to_missing?(method_name, include_private = false)
method_name.to_s.start_with?('on_') || super
end
end
# Register handlers using dynamic methods
EventHandler.on_user_login { |user| puts "User #{user} logged in" }
EventHandler.on_user_logout { |user| puts "User #{user} logged out" }
EventHandler.on_user_login { |user| puts "Updating last seen for #{user}" }
EventHandler.trigger(:user_login, "alice")
# => User alice logged in
# => Updating last seen for alice
puts EventHandler.handler_count(:user_login) # => 2
puts EventHandler.handler_count # => 3
Class variables can implement sophisticated caching and memoization patterns that persist across instance creation and destruction. These patterns optimize expensive computations by storing results at the class level.
class DataProcessor
@@cache = {}
@@cache_stats = { hits: 0, misses: 0 }
def self.process(data)
cache_key = generate_cache_key(data)
if @@cache.key?(cache_key)
@@cache_stats[:hits] += 1
@@cache[cache_key]
else
@@cache_stats[:misses] += 1
result = expensive_operation(data)
@@cache[cache_key] = result
result
end
end
def self.cache_stats
total = @@cache_stats[:hits] + @@cache_stats[:misses]
hit_rate = total > 0 ? (@@cache_stats[:hits].to_f / total * 100).round(2) : 0
{
hits: @@cache_stats[:hits],
misses: @@cache_stats[:misses],
hit_rate: "#{hit_rate}%",
cached_items: @@cache.size
}
end
def self.clear_cache
@@cache.clear
@@cache_stats = { hits: 0, misses: 0 }
end
private
def self.generate_cache_key(data)
case data
when String
"string:#{data.hash}"
when Array
"array:#{data.hash}"
when Hash
"hash:#{data.sort.hash}"
else
"object:#{data.class}:#{data.hash}"
end
end
def self.expensive_operation(data)
# Simulate expensive computation
sleep(0.1)
"processed_#{data}"
end
end
# Usage demonstrates caching effectiveness
DataProcessor.process("test") # Cache miss, takes time
DataProcessor.process("test") # Cache hit, fast
DataProcessor.process(["a", "b"]) # Cache miss
DataProcessor.process(["a", "b"]) # Cache hit
puts DataProcessor.cache_stats
# => {:hits=>2, :misses=>2, :hit_rate=>"50.0%", :cached_items=>2}
Common Pitfalls
Class variables share state across all subclasses, creating unexpected behavior when inheritance hierarchies modify shared data. Unlike instance variables that remain isolated per object, class variables propagate changes throughout the entire class family, often causing confusion in large applications.
class Animal
@@count = 0
def initialize
@@count += 1
end
def self.count
@@count
end
end
class Dog < Animal
end
class Cat < Animal
end
# All classes share the same @@count variable
Dog.new
Cat.new
puts Animal.count # => 2 (expects 0)
puts Dog.count # => 2 (expects 1)
puts Cat.count # => 2 (expects 1)
The sharing behavior becomes more problematic when subclasses need independent counters or state tracking. Class instance variables provide better isolation by maintaining separate state per class in the hierarchy.
# Better approach using class instance variables
class Animal
@count = 0
def initialize
self.class.increment_count
end
def self.increment_count
@count += 1
end
def self.count
@count
end
end
class Dog < Animal
@count = 0 # Separate counter for Dog
end
class Cat < Animal
@count = 0 # Separate counter for Cat
end
Dog.new
Cat.new
puts Animal.count # => 0
puts Dog.count # => 1
puts Cat.count # => 1
Class variables require explicit declaration before use, but Ruby's behavior around undefined variables can cause confusion. Accessing an undefined class variable raises NameError
, while attempting to use the variable in conditional contexts can produce unexpected results.
class ConfigReader
# Missing @@config declaration
def self.get_config
@@config ||= load_default_config # NameError: uninitialized class variable
end
private
def self.load_default_config
{ timeout: 30, retries: 3 }
end
end
# Fix requires explicit declaration
class ConfigReader
@@config = nil # Explicit declaration
def self.get_config
@@config ||= load_default_config # Now works correctly
end
end
Thread safety issues arise when multiple threads access and modify class variables simultaneously. Class variables lack built-in synchronization, making them vulnerable to race conditions in concurrent environments.
class Counter
@@value = 0
def self.increment
# Race condition: read-modify-write operation
current = @@value
@@value = current + 1
end
def self.value
@@value
end
end
# Concurrent access can lose increments
threads = 10.times.map do
Thread.new do
100.times { Counter.increment }
end
end
threads.each(&:join)
puts Counter.value # Often less than 1000 due to race conditions
Thread-safe implementations require explicit synchronization mechanisms like mutexes or atomic operations to prevent data corruption.
require 'thread'
class ThreadSafeCounter
@@value = 0
@@mutex = Mutex.new
def self.increment
@@mutex.synchronize do
@@value += 1
end
end
def self.value
@@mutex.synchronize do
@@value
end
end
end
# Now safe for concurrent access
threads = 10.times.map do
Thread.new do
100.times { ThreadSafeCounter.increment }
end
end
threads.each(&:join)
puts ThreadSafeCounter.value # Always 1000
Class variable visibility differs from instance variable behavior, creating confusion about access patterns. Class variables remain accessible from both class methods and instance methods, while class instance variables only work within class method contexts.
class AccessExample
@@class_var = "shared"
@class_instance_var = "per_class"
def self.class_method
puts @@class_var # Works
puts @class_instance_var # Works
end
def instance_method
puts @@class_var # Works
puts @class_instance_var # nil (different @var)
end
end
AccessExample.class_method # Prints both values
AccessExample.new.instance_method # Only prints class variable
Reference
Class Variable Declaration
Syntax | Scope | Inheritance | Access |
---|---|---|---|
@@variable |
Class and subclasses | Shared across hierarchy | Class and instance methods |
@@variable = value |
Declares and initializes | Propagates to subclasses | Read/write from any context |
Class Method Definition
Method | Syntax | Context | Inheritance |
---|---|---|---|
def self.method_name |
Define in class body | Class method | Inherited by subclasses |
class << self; def method_name |
Singleton class syntax | Class method | Inherited by subclasses |
define_singleton_method |
Dynamic definition | Runtime creation | Not inherited |
Access Patterns
Context | Class Variables | Class Instance Variables | Constants |
---|---|---|---|
Class methods | @@var |
@var |
CONST |
Instance methods | @@var |
Not accessible | CONST |
Subclasses | Shared @@var |
Separate @var |
Inherited CONST |
Common Methods
Method | Parameters | Returns | Description |
---|---|---|---|
class_variable_defined?(symbol) |
Symbol or string | Boolean | Checks if class variable exists |
class_variable_get(symbol) |
Symbol or string | Object | Retrieves class variable value |
class_variable_set(symbol, value) |
Symbol/string, value | Object | Sets class variable value |
class_variables(inherit=true) |
Boolean | Array | Lists class variable names |
remove_class_variable(symbol) |
Symbol or string | Object | Removes class variable |
Error Types
Exception | Cause | Prevention |
---|---|---|
NameError |
Undefined class variable access | Declare before use |
FrozenError |
Modifying frozen class variable | Check frozen status |
ArgumentError |
Invalid variable name format | Use valid @@name syntax |
Thread Safety Considerations
Operation | Thread Safe | Synchronization Required |
---|---|---|
Read class variable | No | Mutex or atomic operations |
Write class variable | No | Mutex or atomic operations |
Class method calls | Depends | Method-specific analysis |
Class variable initialization | No | Ensure single initialization |
Memory and Performance
Aspect | Behavior | Impact |
---|---|---|
Storage | Per class hierarchy | Shared memory usage |
Garbage collection | Referenced by class | Persists with class |
Access speed | Direct reference | Fast access |
Inheritance cost | Propagates through hierarchy | Linear with depth |
Best Practices Summary
Pattern | Use Case | Alternative |
---|---|---|
Class variables | Cross-instance state | Class instance variables |
Class methods | Factory patterns | Instance factory methods |
Mutex synchronization | Thread safety | Concurrent data structures |
Conditional initialization | Lazy loading | Initialize at class load |
Module inclusion | Shared behavior | Composition patterns |