CrackedRuby logo

CrackedRuby

Class Variables and Methods

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