CrackedRuby logo

CrackedRuby

Class Variables (@@variable)

Overview

Class variables store data that belongs to a class rather than individual instances. Ruby creates a single copy of each class variable that all instances of the class share, along with instances of any subclasses. The @@ prefix identifies class variables, distinguishing them from instance variables (@) and local variables.

Ruby initializes class variables when the class definition first executes. The variable exists throughout the program's lifetime once created, maintaining its value across all instances and method calls. Class variables differ from class instance variables by sharing state with subclasses rather than maintaining separate copies.

class Counter
  @@count = 0
  
  def initialize
    @@count += 1
  end
  
  def total
    @@count
  end
end

first = Counter.new
# => @@count is now 1

second = Counter.new  
# => @@count is now 2

first.total  # => 2
second.total # => 2

Ruby's class variable implementation creates a single storage location shared across the entire class hierarchy. This sharing behavior extends to subclasses, which inherit access to parent class variables. The visibility rules allow access from class methods, instance methods, and class bodies.

class Vehicle
  @@wheels = 4
  
  def self.wheels
    @@wheels
  end
  
  def wheels
    @@wheels
  end
end

class Motorcycle < Vehicle
  @@wheels = 2  # Modifies the shared @@wheels variable
end

Vehicle.wheels    # => 2 (changed by subclass)
Motorcycle.wheels # => 2

Class variables provide shared counters, configuration settings, and cached data across all instances. They serve as a mechanism for maintaining state that transcends individual object lifetimes while remaining encapsulated within the class hierarchy.

Basic Usage

Class variable declaration requires initialization within the class body before first use. Ruby raises a NameError when accessing uninitialized class variables, unlike instance variables which return nil. The initialization typically occurs at the top level of the class definition.

class DatabaseConnection
  @@max_connections = 100
  @@active_connections = 0
  
  def initialize
    if @@active_connections >= @@max_connections
      raise "Connection limit exceeded"
    end
    @@active_connections += 1
  end
  
  def close
    @@active_connections -= 1
  end
  
  def self.active_count
    @@active_connections
  end
  
  def self.max_count
    @@max_connections
  end
end

conn1 = DatabaseConnection.new
conn2 = DatabaseConnection.new
DatabaseConnection.active_count # => 2

conn1.close
DatabaseConnection.active_count # => 1

Reading and writing class variables follows standard assignment syntax within any method scope. Class methods access class variables directly, while instance methods can read and modify the shared values. The defined? operator checks for class variable existence.

class Settings
  @@debug_mode = false
  
  def self.enable_debug
    @@debug_mode = true
  end
  
  def self.debug_enabled?
    @@debug_mode
  end
  
  def log(message)
    if @@debug_mode
      puts "[DEBUG] #{message}"
    end
  end
  
  def self.has_setting?(name)
    defined?(@@debug_mode) ? true : false
  end
end

Settings.debug_enabled? # => false
settings = Settings.new
settings.log("Test")    # => (no output)

Settings.enable_debug
settings.log("Test")    # => [DEBUG] Test

Class variables maintain their values between method calls and across different instances. Ruby preserves the state until the program terminates or the class gets redefined. This persistence makes class variables suitable for counters, flags, and shared configuration data.

class RequestTracker
  @@request_count = 0
  @@error_count = 0
  
  def process_request
    @@request_count += 1
    
    if rand < 0.1  # 10% error rate
      @@error_count += 1
      raise "Request failed"
    end
    
    "Success"
  rescue
    "Error"
  end
  
  def self.stats
    {
      total: @@request_count,
      errors: @@error_count,
      success_rate: (@@request_count - @@error_count).to_f / @@request_count
    }
  end
end

tracker = RequestTracker.new
100.times { tracker.process_request }
RequestTracker.stats
# => {:total=>100, :errors=>8, :success_rate=>0.92}

Inheritance creates sharing relationships where subclasses access the same class variable storage as their parent classes. Modifications in any class within the hierarchy affect all related classes. This behavior differs from other object-oriented languages where subclasses typically get independent copies.

class Animal
  @@population = 0
  
  def initialize
    @@population += 1
  end
  
  def self.population
    @@population
  end
end

class Dog < Animal
end

class Cat < Animal
end

Animal.population # => 0
Dog.new
Animal.population # => 1
Cat.new
Cat.new
Animal.population # => 3
Dog.population    # => 3 (shares same variable)

Advanced Usage

Class variable inheritance creates complex sharing patterns when subclasses redefine variables with the same name. Ruby maintains a single variable per name within the class hierarchy, meaning subclass assignments modify the parent's variable rather than creating independent storage.

class ConfigManager
  @@environment = "development"
  @@features = {}
  
  def self.set_feature(name, enabled)
    @@features[name] = enabled
  end
  
  def self.feature_enabled?(name)
    @@features[name] || false
  end
  
  def self.environment
    @@environment
  end
end

class ProductionConfig < ConfigManager
  @@environment = "production"  # Modifies parent's @@environment
  
  def self.setup
    set_feature(:logging, true)
    set_feature(:caching, true)
    set_feature(:debug, false)
  end
end

class TestConfig < ConfigManager
  def self.setup
    set_feature(:logging, false)
    set_feature(:debug, true)
  end
end

ProductionConfig.setup
ConfigManager.environment      # => "production" (changed by subclass)
ConfigManager.feature_enabled?(:logging) # => true

TestConfig.setup
ProductionConfig.feature_enabled?(:debug) # => true (shared state)

Metaprogramming with class variables enables dynamic variable creation and access. The class_variable_defined?, class_variable_get, and class_variable_set methods provide programmatic control over class variables. These methods accept symbols or strings as variable names.

class DynamicRegistry
  def self.register(type, implementation)
    var_name = "@@#{type}_registry"
    
    unless class_variable_defined?(var_name)
      class_variable_set(var_name, {})
    end
    
    registry = class_variable_get(var_name)
    registry[implementation.name] = implementation
  end
  
  def self.get_implementations(type)
    var_name = "@@#{type}_registry"
    
    if class_variable_defined?(var_name)
      class_variable_get(var_name)
    else
      {}
    end
  end
  
  def self.list_types
    class_variables.map { |var| var.to_s.gsub(/@@|_registry/, '') }
  end
end

class FileProcessor; end
class ImageProcessor; end
class VideoProcessor; end

DynamicRegistry.register(:processor, FileProcessor)
DynamicRegistry.register(:processor, ImageProcessor)
DynamicRegistry.register(:handler, VideoProcessor)

DynamicRegistry.get_implementations(:processor)
# => {"FileProcessor"=>FileProcessor, "ImageProcessor"=>ImageProcessor}

DynamicRegistry.list_types # => ["processor", "handler"]

Class variable scope resolution follows Ruby's method lookup chain, but with sharing semantics that create unexpected behaviors. When multiple classes in a hierarchy define variables with identical names, they reference the same storage location regardless of where the access occurs.

module Trackable
  def self.included(base)
    base.class_eval do
      @@tracked_methods = []
    end
  end
  
  def track_method(method_name)
    @@tracked_methods << method_name unless @@tracked_methods.include?(method_name)
  end
  
  def tracked_methods
    @@tracked_methods
  end
end

class UserModel
  include Trackable
  
  def save
    track_method(:save)
    # save logic
  end
  
  def update
    track_method(:update)
    # update logic
  end
end

class ProductModel
  include Trackable
  
  def save
    track_method(:save)
    # save logic
  end
  
  def delete
    track_method(:delete)
    # delete logic
  end
end

user = UserModel.new
user.save
user.update
user.tracked_methods # => [:save, :update]

product = ProductModel.new
product.save
product.delete
product.tracked_methods # => [:save, :update, :delete] (shared with UserModel)

Class reopening and monkey patching affect existing class variables. When reopening a class that already contains class variables, the existing variables remain accessible and modifiable. New class variables can be added to existing classes through reopening.

class Logger
  @@log_level = :info
  
  def self.log_level
    @@log_level
  end
  
  def self.log_level=(level)
    @@log_level = level
  end
  
  def log(message)
    puts "[#{@@log_level.upcase}] #{message}"
  end
end

# Later in the application
class Logger
  @@max_file_size = 1024 * 1024  # Add new class variable
  @@current_file_size = 0
  
  def self.rotate_if_needed
    if @@current_file_size > @@max_file_size
      # rotation logic
      @@current_file_size = 0
    end
  end
  
  def log(message)
    super(message)
    @@current_file_size += message.length
    self.class.rotate_if_needed
  end
end

logger = Logger.new
Logger.log_level = :debug
logger.log("Test message")  # Uses modified behavior

Thread Safety & Concurrency

Class variables create shared mutable state across all threads, making them inherently unsafe for concurrent access without proper synchronization. Multiple threads can read and modify class variables simultaneously, leading to race conditions and data corruption.

require 'thread'

class Counter
  @@count = 0
  @@mutex = Mutex.new
  
  def self.unsafe_increment
    current = @@count
    sleep(0.001)  # Simulate processing time
    @@count = current + 1
  end
  
  def self.safe_increment
    @@mutex.synchronize do
      current = @@count
      sleep(0.001)
      @@count = current + 1
    end
  end
  
  def self.count
    @@count
  end
  
  def self.reset
    @@mutex.synchronize { @@count = 0 }
  end
end

# Demonstrate race condition
Counter.reset
threads = 10.times.map do
  Thread.new { 100.times { Counter.unsafe_increment } }
end
threads.each(&:join)
puts "Unsafe result: #{Counter.count}"  # Usually less than 1000

# Demonstrate thread safety
Counter.reset
threads = 10.times.map do
  Thread.new { 100.times { Counter.safe_increment } }
end
threads.each(&:join)
puts "Safe result: #{Counter.count}"    # Always 1000

Read-modify-write operations on class variables require atomic protection to prevent lost updates. Simple assignment operations are atomic, but complex modifications involving multiple steps need synchronization mechanisms like mutexes or atomic operations.

class StatisticsTracker
  @@stats = { requests: 0, errors: 0, total_time: 0.0 }
  @@mutex = Mutex.new
  
  def self.record_request(duration, error = false)
    @@mutex.synchronize do
      @@stats[:requests] += 1
      @@stats[:errors] += 1 if error
      @@stats[:total_time] += duration
    end
  end
  
  def self.get_stats
    @@mutex.synchronize do
      {
        requests: @@stats[:requests],
        errors: @@stats[:errors],
        error_rate: @@stats[:errors].to_f / @@stats[:requests],
        average_time: @@stats[:total_time] / @@stats[:requests]
      }
    end
  end
  
  def self.reset_stats
    @@mutex.synchronize do
      @@stats = { requests: 0, errors: 0, total_time: 0.0 }
    end
  end
end

# Thread-safe usage
threads = 50.times.map do
  Thread.new do
    100.times do
      duration = rand * 0.1
      error = rand < 0.05
      StatisticsTracker.record_request(duration, error)
    end
  end
end

threads.each(&:join)
puts StatisticsTracker.get_stats

Class hierarchies with shared class variables require careful consideration of synchronization scope. Subclasses sharing parent class variables must use the same synchronization mechanisms to maintain consistency across the inheritance chain.

class BaseService
  @@active_requests = 0
  @@request_mutex = Mutex.new
  
  def self.start_request
    @@request_mutex.synchronize do
      @@active_requests += 1
    end
  end
  
  def self.end_request
    @@request_mutex.synchronize do
      @@active_requests -= 1
    end
  end
  
  def self.active_count
    @@request_mutex.synchronize { @@active_requests }
  end
  
  def process
    self.class.start_request
    begin
      yield if block_given?
    ensure
      self.class.end_request
    end
  end
end

class UserService < BaseService
  def handle_user_request(user_id)
    process do
      # User-specific processing
      sleep(rand * 0.1)
    end
  end
end

class OrderService < BaseService
  def handle_order_request(order_id)
    process do
      # Order-specific processing
      sleep(rand * 0.1)
    end
  end
end

# Both services share the same counter and mutex
user_threads = 10.times.map do
  Thread.new do
    service = UserService.new
    5.times { service.handle_user_request(rand(1000)) }
  end
end

order_threads = 10.times.map do
  Thread.new do
    service = OrderService.new
    5.times { service.handle_order_request(rand(1000)) }
  end
end

(user_threads + order_threads).each(&:join)
puts "Final active count: #{BaseService.active_count}"  # Should be 0

Common Pitfalls

Class variable inheritance creates unexpected sharing behaviors where subclass modifications affect parent classes and sibling classes. Developers often expect subclasses to have independent class variables, but Ruby shares the same storage location across the entire hierarchy.

class Animal
  @@sound = "generic sound"
  
  def self.sound
    @@sound
  end
  
  def self.sound=(new_sound)
    @@sound = new_sound
  end
  
  def speak
    puts @@sound
  end
end

class Dog < Animal
  @@sound = "woof"  # This modifies Animal's @@sound!
end

class Cat < Animal
  @@sound = "meow"  # This also modifies Animal's @@sound!
end

# Unexpected behavior:
Animal.sound # => "meow" (not "generic sound")
Dog.sound    # => "meow" (not "woof")
Cat.sound    # => "meow"

dog = Dog.new
dog.speak    # => "meow" (not "woof")

# The last assignment wins for all classes

Class variable scope differs from constant scope, leading to confusion when mixing these mechanisms. Constants create separate copies in subclasses, while class variables share storage. This difference causes inconsistent behavior when refactoring between constants and class variables.

class Configuration
  # Using constants - each class gets its own copy
  DEFAULT_TIMEOUT = 30
  
  # Using class variables - shared across hierarchy
  @@current_timeout = 30
  
  def self.timeout
    @@current_timeout
  end
  
  def self.timeout=(value)
    @@current_timeout = value
  end
end

class WebConfiguration < Configuration
  DEFAULT_TIMEOUT = 60      # Independent constant
  @@current_timeout = 60    # Modifies parent's class variable
end

class ApiConfiguration < Configuration
  DEFAULT_TIMEOUT = 10      # Independent constant
  @@current_timeout = 10    # Modifies parent's class variable
end

# Constants behave as expected:
Configuration::DEFAULT_TIMEOUT    # => 30
WebConfiguration::DEFAULT_TIMEOUT # => 60
ApiConfiguration::DEFAULT_TIMEOUT # => 10

# Class variables share state unexpectedly:
Configuration.timeout    # => 10 (modified by ApiConfiguration)
WebConfiguration.timeout # => 10 (shares same variable)
ApiConfiguration.timeout # => 10

Module inclusion creates additional sharing complexities when modules define class variables. Multiple classes including the same module share the module's class variables, creating hidden coupling between unrelated classes.

module Cacheable
  def self.included(base)
    base.class_eval do
      @@cache = {}
    end
  end
  
  def cache_get(key)
    @@cache[key]
  end
  
  def cache_set(key, value)
    @@cache[key] = value
  end
  
  def cache_size
    @@cache.size
  end
end

class User
  include Cacheable
  
  def initialize(name)
    @name = name
    cache_set("user_#{name}", self)
  end
end

class Product
  include Cacheable
  
  def initialize(sku)
    @sku = sku
    cache_set("product_#{sku}", self)
  end
end

# Unexpected sharing between unrelated classes:
user = User.new("john")
user.cache_size # => 1

product = Product.new("ABC123")
product.cache_size # => 2  (shares cache with User!)

user.cache_size    # => 2  (now includes product data)

# User cache contains product data:
user.cache_get("product_ABC123") # => #<Product:...>

Class variable initialization timing can cause errors when accessed before definition. Unlike instance variables that return nil when uninitialized, class variables raise NameError exceptions. This behavior creates order-dependency issues in class definitions.

class ProblematicService
  def self.configure(options)
    @@options = options  # First assignment initializes the variable
  end
  
  def self.get_option(key)
    @@options[key]  # NameError if called before configure
  end
  
  def initialize
    # This will fail if configure hasn't been called:
    @timeout = @@options[:timeout]
  end
end

# This raises NameError:
begin
  ProblematicService.get_option(:timeout)
rescue NameError => e
  puts e.message # => "uninitialized class variable @@options"
end

# Safer approach with initialization guard:
class SaferService
  @@options = {}  # Initialize immediately
  
  def self.configure(options)
    @@options.merge!(options)
  end
  
  def self.get_option(key)
    @@options[key]
  end
  
  def initialize
    @timeout = @@options.fetch(:timeout, 30)  # Provide default
  end
end

Reference

Class Variable Syntax

Syntax Purpose Example
@@variable Declare/access class variable @@count = 0
@@variable = value Assignment @@name = "default"
defined?(@@variable) Check existence defined?(@@count)
self.class.class_variables List all class variables Returns array of symbols

Core Methods

Method Parameters Returns Description
class_variable_defined?(symbol) symbol (Symbol/String) Boolean Check if class variable exists
class_variable_get(symbol) symbol (Symbol/String) Object Get class variable value
class_variable_set(symbol, value) symbol (Symbol/String), value (Object) Object Set class variable value
class_variables(inherit=true) inherit (Boolean) Array List class variable names
remove_class_variable(symbol) symbol (Symbol/String) Object Remove class variable

Inheritance Behavior

Scenario Behavior Example Result
Parent defines @@var Child accesses same storage Shared value
Child modifies @@var Parent sees modification Modified value
Multiple children modify @@var Last modification wins Latest value
Module defines @@var All including classes share Shared storage

Scope Access Rules

Context Access Modification Notes
Class body Read/Write Yes Direct access during definition
Class method Read/Write Yes Use @@variable syntax
Instance method Read/Write Yes Use @@variable syntax
Subclass Read/Write Yes Shares parent's storage
Outside class No No Private to class hierarchy

Common Error Types

Error Cause Solution
NameError Accessing uninitialized class variable Initialize in class body
TypeError Wrong symbol type for metaprogramming methods Use Symbol or String
Data corruption Concurrent access without synchronization Use Mutex or atomic operations
Unexpected sharing Subclass modifications affect parents Use class instance variables instead

Thread Safety Considerations

Operation Thread Safe Recommendation
Simple assignment Yes @@var = value
Read access Yes @@var
Read-modify-write No Use Mutex.synchronize
Hash/Array modification No Synchronize entire operation
Conditional assignment No Use Mutex for atomicity

Alternative Patterns

Pattern Use Case Implementation
Class instance variables Independent subclass state @var in class body
Constants Immutable configuration CONSTANT = value
Module configuration Shared settings Module with accessor methods
Singleton pattern Global state Class with instance management