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
.