Ora

What are class variables in Ruby?

Published in Ruby Variables 5 mins read

Class variables in Ruby are a type of variable that are shared across an entire class and all of its subclasses. They provide a way for all instances of a class, and any derived classes, to access and modify a single, common value.

What Are Class Variables?

In Ruby, class variables are denoted by a double at-sign (@@) preceding their name, like @@my_variable. Unlike instance variables (which belong to a specific object) or local variables (which exist only within a method or block), class variables maintain a single, shared value. This means that if a class variable's value is changed, that change is immediately visible to all instances of the class and any classes that inherit from it.

Key Characteristics

  • Shared Scope: They are shared among the class they are defined in and all of its subclasses. This is their most distinctive feature.
  • Global to Class Hierarchy: A single copy of the variable exists for the entire class hierarchy.
  • Mutation Affects All: Any modification to a class variable, whether made from a class method, an instance method, or within a subclass, will update the single shared value.
  • Declaration: Must be declared and initialized before being used, typically at the class level or within a class method.

For more information, you can refer to the Ruby documentation on variables.

Class Variables vs. Other Ruby Variables

Understanding the distinct scope of class variables is easier when compared to other variable types in Ruby:

Variable Type Prefix Scope Shared? Example
Local Variable None Method, block, or loop No, specific to its scope name
Instance Variable @ Specific object/instance No, each object has its own copy @user_id
Class Variable @@ Class and all its subclasses Yes, single value for hierarchy @@population_count
Global Variable $ Entire Ruby program Yes, single value for the whole program $LOAD_PATH

Practical Examples of Class Variables

Let's look at how class variables behave in Ruby.

Example 1: Basic Usage and Sharing Across Instances

Consider a scenario where you want to track the number of Car objects created.

class Car
  @@number_of_cars = 0 # Initialize class variable

  def initialize(make, model)
    @make = make
    @model = model
    @@number_of_cars += 1 # Increment every time a new Car is created
  end

  def self.total_cars # Class method to access the class variable
    @@number_of_cars
  end

  def car_details
    "#{@@number_of_cars} cars exist. This car is a #{@make} #{@model}."
  end
end

car1 = Car.new("Toyota", "Corolla")
puts "Total cars via class method: #{Car.total_cars}" # Output: 1

car2 = Car.new("Honda", "Civic")
puts "Total cars via instance method: #{car1.car_details}" # Output: 2 cars exist. This car is a Toyota Corolla.
puts "Total cars via class method: #{Car.total_cars}" # Output: 2

car3 = Car.new("Ford", "Focus")
puts "Total cars via class method: #{Car.total_cars}" # Output: 3

In this example, @@number_of_cars increments with each new Car instance, and its value is consistent whether accessed through a class method (Car.total_cars) or an instance method (car1.car_details).

Example 2: Sharing Across Subclasses

Class variables are also shared with subclasses.

class Vehicle
  @@vehicle_count = 0

  def initialize
    @@vehicle_count += 1
  end

  def self.count
    @@vehicle_count
  end
end

class Truck < Vehicle
  @@truck_count = 0 # Can have its own class variable too

  def initialize
    super # Call parent's initialize to increment @@vehicle_count
    @@truck_count += 1
  end

  def self.truck_count
    @@truck_count
  end
end

class Motorcycle < Vehicle
  def initialize
    super
  end
end

vehicle1 = Vehicle.new
truck1 = Truck.new
motorcycle1 = Motorcycle.new
truck2 = Truck.new

puts "Total Vehicles (@@vehicle_count): #{Vehicle.count}"      # Output: 4
puts "Total Trucks (@@truck_count): #{Truck.truck_count}"      # Output: 2
puts "Total Vehicles via Truck class: #{Truck.count}"          # Output: 4 (Truck inherits @@vehicle_count)
puts "Total Vehicles via Motorcycle class: #{Motorcycle.count}" # Output: 4 (Motorcycle inherits @@vehicle_count)

Here, @@vehicle_count is incremented by instances of Vehicle, Truck, and Motorcycle because they all share the same class variable defined in Vehicle. Truck also has its own class variable @@truck_count which is separate.

When to Use Class Variables

Class variables are best suited for situations where you need:

  • Global Class-Level Counters: As seen in the Car example, to track the number of objects created for a class or its descendants.
  • Shared Configuration: Storing settings or constants that should apply to all instances and subclasses of a particular hierarchy, and potentially be mutable during runtime.
  • Caching: A simple form of caching data that should be available to the entire class structure.
  • Resource Management: Keeping track of shared resources or state that needs to be synchronized across related objects.

Potential Pitfalls

While powerful, the shared nature of class variables can sometimes lead to unexpected behavior, especially in complex inheritance hierarchies. Since a change in one subclass affects the class variable for all other subclasses and the parent class, it's crucial to manage their state carefully. Overuse can make code harder to debug and understand.

It's often recommended to consider alternatives like class instance variables (@variable used within class methods, e.g., self.instance_variable_set(:@my_var, value)) or modules for more controlled sharing, especially if you want to avoid the inheritance sharing behavior of @@variables.