Key Takeaways
- Class variables in Ruby are often seen as problematic due to their behavior when inheritance is involved, as changes made to a class variable in a subclass can inadvertently affect the superclass.
- Despite this, class variables can be useful in certain scenarios, such as in web applications where they can be used for initialization data and class level configuration.
- A common technique to avoid issues with class variables is to use class instance variables, which are not shared with subclasses and therefore changes in one class do not affect others.
- Understanding and implementing class variables effectively requires a strong grasp of Ruby’s object-oriented nature, as well as the differences between class variables, instance variables, and constants.
Class variables in Ruby have a bad name. It’s true, they are considered harmful, borderline evil. So why the bad rap? Well, most groans are about how they behave when inheritance enters the fray. Correction: It’s all about how they behave compared to what we expect.
To paint a picture of our little problem child lets look at what we expect with PHP.
<?php
class DemoClass {
public $myvar;
public function __construct() {
$this->myvar = "hello world : ";
}
public function getMyVar() {
echo $this->myvar;
}
}
class Demo2Class extends DemoClass {
public function __construct() {
$this->myvar = "goodbye world : ";
}
}
$demo1 = new DemoClass();
$demo2 = new Demo2Class();
$demo1->getMyVar();
$demo2->getMyVar();
$demo1->getMyVar();
// Produces
// hello world : goodbye world : hello world : "
That makes sense to me. We define a class with a concrete constructor and abstract getter. When inherited we can override the constructor and set the “class variable” to something else. The base class is unaffected, the world keeps spinning.
In Ruby if we try and do something similar using class variables we end up with one or two problems.
class DemoClass
@@my_var = nil
def initialize
@@my_var = "hello world"
end
def my_var
puts @@my_var
end
end
class Demo2Class < DemoClass
def initialize
@@my_var = "goodby world"
end
end
demo1 = DemoClass.new
demo1.my_var
demo2 = Demo2Class.new
demo2.my_var
demo1.my_var
# Produces
#hello world
#goodbye world
#goodbye world
When you approach class variables in Ruby in this manner they just seem wrong. The change to my_var
in Demo2Class
has bubbled up to our base class. To mitigate the problem, say we subclass DemoClass
again the behaviour of the class variable my_var
is nothing short of unpredictable. They are global variables in disguise.
So Why Use Class Variables?
On the face of it the rumours about class variables are true. But now we know what not to do, is there a scenario where class level variables are actually useful? Well, I confess. I use them in almost every web application.
Using the methods described in our examples, depending on initialization data implemented using class level variables as a class ‘state’ is a pretty bad idea. However, I do use class variables for initialization data.
Rails applications generally have at least 4 environments, production, staging, development and test. It’s very likely that configuration changes per environment, be it email addresses to mail exceptions to or a URL of a web service sandbox. The code itself looks like:
class AppConfig
@@app_config = YAML::load(ERB.new((IO.read("#{RAILS_ROOT}/config/app_config.yml"))).result)[RAILS_ENV]
def self.method_missing(sym, *args, &block)
@@app_config[sym.to_s]
end
end
It should be pretty self explanatory. A YAML file is parsed into the class variableapp_config. Then using
method_missingat class level I can retrieve values easily with
AppConfig.exception_email_addresses`.
Class Level Configuration
So now hopefully we can agree that class variables are not completely evil. Sometimes we do want to share traits at a class level down class hierarchy. A common technique to achieve this without incurring the problems described previously is to use class instance variables.
Although a bit of a mouthful, class instance variables are pretty straightforward, (coming from PHP they did not make sense at first) we just have to adjust our thinking slightly. I always find the best way to discover first is irb
.
ruby-1.9.2-p290 :001 > class Demo
ruby-1.9.2-p290 :002?> @my_instance_variable = "whaaa?"
ruby-1.9.2-p290 :003?> end
=> "whaaa?"
ruby-1.9.2-p290 :004 > Demo.class
=> Class
ruby-1.9.2-p290 :005 > Demo.superclass
=> Object
ruby-1.9.2-p290 :006 > Demo.instance_variables
=> [:my_instance_variable]
There it is, unlike more classical languages Ruby classes (like everything else) are just objects with instance variables. So how does this knowledge help us? Returning to the previous example and employing a little accessors trick.
class DemoClass
class << self
attr_accessor :my_var
end
@my_var = nil
def initialize
self.class.my_var = "hello world"
end
def my_var
puts self.class.my_var
end
end
class Demo2Class < DemoClass
def initialize
self.class.my_var = "goodby world"
end
end
demo1 = DemoClass.new
demo1.my_var
demo2 = Demo2Class.new
demo2.my_var
demo1.my_var
# Produces
# hello world
# goodbye world
# hello world
The example is looking pretty horrible now, before any explanation lets clean it up so it offends our eyes no longer.
class DemoClass
class << self
attr_accessor :my_var
end
@my_var = "hello world"
end
class Demo2Class < DemoClass
@my_var = "goodby world"
end
puts DemoClass.my_var
puts Demo2Class.my_var
puts DemoClass.my_var
If you are unsure about eigenclasses ( a.k.a singleton classes, but I’m originally a PHP guy and a singleton class meant something totally different at first) in this demonstration we are simply using it to create a attr_accessor
as we normally would, only doing it this way creates it for our class instance variable.
Wrapping up
Remember when I said class variables are not always bad and showed an example when they are perfectly acceptable (well in my book anyway). I did come across a problem with that AppConfig
snippet.
I had to duplicate an application for two different clients. Obviously the configuration data was different, but the codebase was to be the same. I wanted to maintain the codebase in a single SCM repository (bug fixes or updates would apply to both versions) and I didn’t want to hit git merge mashes, in fact I didn’t want the SCM to take on a new role in my workflow, it’s there to do SCM only, not manage configuration across clients.
Obviously, we would look to persist the configuration in a database, which is simple to achieve in Rails using an ActiveRecord
model like so:
# app/models/application_config.rb
class ApplicationConfig < ActiveRecord::Base
serialize :config_value
after_save :reset_config
def reset_config
AppConfig.reset_configs
end
end
# lib/app_config.rb
class AppConfig
class << self
attr_accessor :configs
end
@configs = {}
def self.method_missing(sym, *args, &block)
@configs[sym.to_s] ||= ""
end
def self.reset_configs
self.configs = {}
configs = ApplicationConfig.all
configs.each do |setting|
self.configs = {setting.config_key => setting.config_value}.merge self.configs
end
end
end
Using the code above I could drop in a persisted configuration for the application without refactoring any code where the configuration data was being accessed (this was a big win as it was a pretty large app). I could also use a simple scaffold to update and edit configuration on the fly. The use of class instance variables allowed me to cache the config data without hitting the database for every method_missing
message the class received.
Hopefully, that has dispelled the class variables are useless/evil myth. Like most things, a lack of understanding and naive implementations gives class variales a bad name. It certainly took me a while (and plenty of naive implementations) to get a grasp of what’s going on with them. As for the eigenclass stuff, check out Nathan Kleyns article on metaprogramming for more detail.
Frequently Asked Questions about Ruby Class Variables
What is the difference between class variables and instance variables in Ruby?
In Ruby, class variables and instance variables serve different purposes. A class variable, denoted by @@, is shared among all instances of a class and the class itself. It means if you change the value of a class variable, it will be changed for all instances of that class. On the other hand, an instance variable, denoted by @, is unique to each instance of a class. Each object or instance of the class has its own copy of the instance variable. Therefore, changing the value of an instance variable only affects that specific instance, not all instances of the class.
How can I access a class variable in Ruby?
In Ruby, class variables are accessed within the class by using the @@ prefix followed by the variable name. However, they are not directly accessible outside the class. To access a class variable outside the class, you need to define a class method that returns the value of the class variable.
Can I use class variables in subclasses in Ruby?
Yes, class variables in Ruby are shared with subclasses. This means that if you define a class variable in a superclass, it will be accessible in any subclasses. However, any changes made to the class variable in the subclass will also affect the superclass, because they both reference the same memory location.
What is the scope of class variables in Ruby?
The scope of class variables in Ruby is within the class and its instances, as well as any subclasses. This means that class variables can be accessed and modified anywhere within the class, its instances, and any subclasses. However, they cannot be accessed directly outside the class.
How do class variables behave in Ruby compared to other languages?
In Ruby, class variables are shared among all instances of a class and the class itself, which is different from some other languages. For example, in Java, class variables (also known as static variables) are shared among all instances of a class, but not with the class itself.
What are the potential pitfalls of using class variables in Ruby?
One potential pitfall of using class variables in Ruby is that they are shared with subclasses. This means that changes to a class variable in a subclass can unintentionally affect the superclass and other subclasses. This can lead to unexpected behavior and bugs that are difficult to track down.
How can I avoid problems with class variables in Ruby?
To avoid problems with class variables in Ruby, you can use class instance variables or constants instead. Class instance variables are not shared with subclasses, so changes in one class do not affect others. Constants are also not shared with subclasses, and they have the added benefit of being immutable, meaning their values cannot be changed once set.
Can I use class variables in modules in Ruby?
Yes, you can use class variables in modules in Ruby. However, just like with classes, class variables in modules are shared among all instances of the module and the module itself. This means that changes to a class variable in one instance of the module will affect all other instances.
How do I define a class variable in Ruby?
In Ruby, you define a class variable by using the @@ prefix followed by the variable name. You can then assign a value to the class variable using the assignment operator (=). For example, @@my_variable = 10 defines a class variable named my_variable and assigns it the value 10.
Can I change the value of a class variable in Ruby?
Yes, you can change the value of a class variable in Ruby. To do this, you simply assign a new value to the class variable using the assignment operator (=). However, keep in mind that changing the value of a class variable will affect all instances of the class and the class itself, as well as any subclasses.
Dave is a web application developer residing in sunny Glasgow, Scotland. He works daily with Ruby but has been known to wear PHP and C++ hats. In his spare time he snowboards on plastic slopes, only reads geek books and listens to music that is certainly not suitable for his age.