When I first started out with object oriented Ruby, I noticed that I could seemingly use instance and class variables interchangeably with their self methods. That is, it seemed like I could arbitrarily replace @instance_var with self.instance_var, and @class_var with self.class_var in instance and class methods respectively, and get the same expected output from my methods. It turns out that @var/@@var and self.var are actually communicating with Ruby in distinct ways, and they may return the same value but this is not always the case. Let?s take a closer look at @/@@ and self to unpack the difference.
What is @/@@?
There are 4 types of variables in Ruby: local , global, instance, and class. The @ symbol before a variable tells Ruby that we are working with an instance variable, and @@ before a variable tells us we are working with a class variable. We use @ before a variable in instance methods within a class to tell Ruby to access that attribute (instance variable) of the instance. Using @instance_var makes instance_var accessible to all instance methods. Similarly, we use @@ before a variable in class methods to tell Ruby to access that attribute (class variable) of the class. Using @@class_var makes class_var accessible to the entire class. When we use @instance_var or @@class_var, we are accessing that instance or class variable directly.
Here is an example:
What is self?
You may have noticed the word self in the definition our class methods. Every object in Ruby has and is aware of its self. The keyword self in Ruby enables you to access to the current object ? the object that is receiving the current message. The word self can be used in the definition of a class method to tell Ruby that the method is for the self, which is in this case the class. Using self inside an instance or class method refers to the same object the method is being called on, i.e., and instance and class respectively. In an instance method such as #say_hi, using self.instance_var tells the object (in this case, an instance of a class) to send itself the message ?instance_var? which, depending on how the class is written, can return @instance_var or something else. In a class method, using self.class_var tells the object (in this case, a class) to send itself the message ?class_var? which, depending on how the class is written, can return @@class_var or something else.
Here is an example:
Wait a minute? didn?t we just replace @instance_var with self.instance_var and @@class_var with self.class_var, and get the same output for our say_hi methods?
It?s not a trick. Let?s find out why we can do this.
Why can we use them interchangeably here?
In this example, we were able to use @class_var and @instance_var interchangeably with the methods self.class_var and self.instance_var because those methods return @class_var and @instance_var respectively. Instead of directly calling the variable, we are going through the #class_var and .instance_var methods, which in this case, also return @@class_var and @instance_var.
So is there a difference?
Yes! While using @ or @@ to access an instance or class variable, we are telling Ruby to access that variable directly. In fact, even if we had instead just initialized @instance_var and @@class_var with those strings, and not written methods #instance var and .class_var to access those variables, we would still be able to call them directly in our say_hi methods since we are directly accessing a variable we initialized.
Take a look:
What if we initialize @instance_var and @@class_var and then try to call them using self.instance_var and self.class_var in our say_hi methods.
Look at the error we get:
That?s helpful feedback. It looks like Ruby is telling us that when we wrote self.instance_variable, Ruby looked for an instance method #instance_variable and didn?t find it. That?s because it doesn?t exist! The same thing happened with self.class_var: we don?t have a class method .class_var, so we get an error. So in summary, we can only use @instance_var and @@class_var interchangeably with self.instance_var and self.class_var if those methods exist and they return that instance or class variable.
Which one should I use?
You might be thinking: ?Why should I go through an instance or class method to get to a variable when I could just access that variable directly?? And you?d be right. There?s not much point going through a method unless that method returns something other than the original variable (this is a common case because we tend to write methods that manipulate our variables, rather than just return them or set them to a constant value).
We typically want give ourselves reading and writing access to an instance variable, so that we can use it in other methods and set its value. For this we can use an attr_accessor, which is shorthand for writing a setter and getter method for the instance variable.
Lets look at a more realistic example:
Since we have an attr_accessor for our instance variable @age, we can still use self.age in the place of @age in out #age_in_dog_years method, however we can see that there isn?t any added clarity or functionality in going through the #age method rather than calling the instance variable directly. Similarly, since we wrote a method self.all that returns @@all, we could have used self.all in the place of @@all in our .num_people method, but again, there is no increased clarity or functionality in going through the self.all method rather than calling the class variable directly.
We do notice though, that our methods #age_in_dog_years and .num_people cannot be accessed via an instance variable because they are methods that rely on other instance variables.
Do we need to explicitly say self?
We noted that in our method #age_in_dog_years, we could have called self.age instead of @age. Since we are in an instance method, Ruby notices that you didn?t pass ?age? in as an argument, or define a variable ?age? within the instance method, so it looks for an instance method called ?age.? It finds the instance method #age (through the attr_accessor), assumes that this is what you meant, and uses the return value of the method, which is @age. We also noted that in our method .num_people, we could have called self.all instead of @@all. Similarly, since we are in a class method, Ruby notices that you didn?t pass ?all? in as an argument, or define a variable ?all? within the class method, so it looks for a class method called ?all.? It finds the class method .all, assumes that this is what you meant, and uses the return value of the method, which is @@all.
It would look like this:
A final note:
Say we decided that instead of calling our class variable ?all? we wanted to call it ?people.? If we used @@all everywhere instead of self.all, we could have to change all those places to say @@people. If instead we used self in the following way to call the class method .all rather than the actual class variable, we would only have to change it in our .all method. Notice how we could even use self to call the class method .all within initialize, if we say ?self.class.all? rather than ?self.all.? We do this to tell Ruby we are talking about the class method ?all,? rather than the instance method #all (which doesn?t exist).
Take a look:
In this way, we can make our code more robust, so that if one variable name changes, we do not have to make many changes throughout our program. On the other hand, calling instance and class variables is more explicit, and doing a find and replace all is not difficult. So at the end of the day, if your method is returning the variable you want, it comes down to user preference whether you use @/@@ or self.
Conclusion
So there you have it: use instance or class variables when you want to access the variable directly, and use self to call an instance or class method. Sometimes instance or class methods return the variable you want. In this case, it?s up to you which to use. However, instance and class methods frequently manipulate instance and class variables, thus calling those methods on objects via the self keyword is common and useful in Ruby.