Understanding Classes, Objects, Methods, Instance Variables, Class Variables, Class Instance Variables and Inheritance in Ruby
Classes and Objects
Introduction
A lot of what is discussed here is only deep enough to get some practical knowledge on how to use these language features. For even more in-depth understanding, refer to the docs or read the ruby source code itself. :)
This tutorial assumes you have some knowledge about programming in some other object oriented programming language already.
Intro to Syntax
In ruby, certain characters in the beginning of a variable denote that variable as being an instance variable, class variable, global variable, etc. Take a look at this list. We’ll discuss them further as we go.
-
a local variable (LVAR) starts without any “special character” other than the underscore.
_foois also a local variable. -
a global variable (GVAR) starts with
$. -
an instance variable (IVAR) starts with a single
@character. -
a class instance variable (CIVAR) also starts with a single
@character. -
a class variable (CVAR) starts with
@@(yes, two).
We’ll learn how do distinguish IVARS from CIVARS later on in the text.
Classes, Objects, Accessors
Let’s create a class with a constructor, a couple of instance variables and, pair of methods and a constructor method.
class Person
# The constructor.
def initialize(name, email)
# @name and @email instance are variables.
@name = name
@email = email
end
# Getter (reader) for email.
def email
@email
end
# Setter (writer) for email.
def email=(email)
@email = email
end
end
# Creates an instance of the Person class.
p1 = Person.new('Linus Torvalds', 'linus@linux.org')
# Prints the email using the `getter`.
p p1.email
# Sets a new email using the `setter`.
p1.email = 'torvalds@kernel.org'
# Prints it again using the `getter`.
p p1.email
"linus@linux.org"
"torvalds@kernel.org"
@name and @email are instance variables. They are private and thus require proper accessor methods to get and set them. Here we define getter/setter for the email only for sake of demonstration.
Note that Ruby instance variables (IVARs) are not declared beforehand as we normally do in other object oriented languages.
Visibility
In Ruby, there is no way to make attributes public. They are always private. All attributes that needed to be manipulated from outside of the class/object need proper accessor methods, like we did for the email in the previous example. There are other ways, though. Follow along!
A Note About Syntax
Note the syntax to create the getter and setter:
def email
@email
def
def email=(new_email)
@email = new_email
end
Did you see how we named both methods with the same name as the attribute itself? Just that the setter has an extra = (equals sign) character. For someone used to some other language, they may think that doing p1.email means that email is a public attribute on the p1 object. But it is not. It just so happens that the getter is named email.
Moreover, most of the times Ruby doesn’t require that you use parenthesis (and rubists generally don’t use parens unless strictly necessary) to invoke methods. So, although p1.email doesn’t look like a method call, it actually is. It is just the same as p1.email().
The same holds true for the setter. You can either do p1.email = 'foo@bar.net or p1.email=('foo@bar.net'). It is really the same thing. Still, the first version makes it look like we are assigning a value to a public property, which we are not. We are using a setter method.
Using attr_reader and attr_writer
Here we use attr_reader and attr_writer methods which in turn, create instance variables for us with proper getters and setters.
class Person
# Define getters.
attr_reader :name, :email
# Define setters.
attr_writer :name, :email
def initialize(name, email)
@name = name
@email = email
end
end
# Creates in instance of the Person class.
luke = Person.new('Luke', 'luke@star.net')
# Uses the email setter.
p luke.email
# Uses the email getter.
luke.email = 'padawan@start.net'
# Uses the email getter again
p luke.email
# We could have used the name getter/setter in the same way.
When we do something like attr_reader :foo, Ruby internally does this for us:
# It creates a getter for @foo.
def foo
@foo
end
And when we write attr_writer :foo, Ruby internally does this:
# It creates a setter for @foo.
def foo=(arg)
@foo = arg
end
Using attr_acessor
And here we use a single attr_acessor to create readers and writers (getters and setters) in a single step.
class Person
attr_accessor :name, :email
end
belinda = Person.new
belinda.name = 'Belinda'
belinda.email = 'belinda@github.io'
p belinda.name
p belinda.email
"Belinda"
"belinda@github.io"
Class Variables
A class that contains a class variable (CVAR) @@count.
class Thing
# This is a CVAR.
@@count = 0
# A class method getter/reader for the CVAR.
def self.count
@@count
end
# A class method setter/writer for the CVAR.
def self.count=(num)
@@count = num
end
end
p Thing.count
Thing.count = 42
p Thing.count
0
42
A class variable is one that belongs to the class, and not to the instances of the class. That is why we do invoke the getter and setter from the Thing class itself. But now things get trickier. We are using self to define the methods. Why? Go to the next section and find out.
Classes are Objects in Ruby
Yes, in Ruby, classes are themselves instances of Object.
class Thing
# I am not very useful...
end
p Thing.superclass
p Thing.object_id
Object
15051340
Thing has an object id, so, it is an object!
self
self is an object that defines the context/scope of methods and variables. self points to an object. What is the value of self? In other words, what object does self point to? It depends. Let’s see an example:
class Thing
def who_am_i?
self
end
end
thing = Thing.new
p thing == thing.who_am_i?
p thing.object_id
p thing.who_am_i?.object_id
true
10455120
10455120
thing is an object (an instance of the class Thing). The method who_am_i? is an instance method. Inside this method we simply return self (and remember: self points to an object). By using the comparison operator == we see that both thing and self (which was returned from the method) are the same thing. Even testing for their object IDs prove that both are the same!
Now let’s see this other example, where self is in the body of the class (and not inside a method):
class Thing
p self
p self.object_id
end
p Thing.object_id
Thing
20384320
20384320
Thing is printed because of p self inside the body of the class. It tells us that self is actually Thing. Then both self.object_id and Thing.object_id return exactly the same value. As a side note, if classes are themselves objects, and self points to an object, we can do self.object_id without problems.
Our testes answer the question “What object does self point to?”, and the answer varies depending on the context/place in the code where self is used:
-
selfinside an instance method points to the instance of the class. -
selfinside the body of the class points to the class itself (and the class is an object as well).
So, if a class has a @@count class variable, we must define class methods if we are to access the class variable. To create such methods, we use self in the body of the class followed by a dot and a method name:
class Thing
# A class variable
@@count = 0
# Class method getter/reader for @@count CVAR.
def self.count
@@count
end
# Class method setter/writter for @@count CVAR.
def self.count=(val)
@@count = val
end
end
If self in the body of the class Thing points to Thing itself (and our methods are defined with def self.method_name) it means that both the getter method count and the setter method count= can now be accessed directly from Thing.
We talked about instance variables and class variables. Let’s create a class called “Monster” that keeps the number of instantiated monsters and accessors for changing the power of the monster when they get hit or healed.
class Monster
@@count = 0
def initialize(name)
@name = name
@power = 100 # Power always start at 100%.
end
# Class method getter/writer for CVAR @@count.
def self.count
@@count
end
# Class method setter/writer for CVAR @@count.
def self.count=(num)
@@count = num
end
# Instance method getter/reader for IVAR @power
def power
@power
end
# Instance method setter/writer for IVAR @power
def power=(val)
@power = val
end
end
# Create a new monster and increment the monster count.
alien = Monster.new('Alien')
Monster.count = Monster.count + 1
predator = Monster.new('Predator')
Monster.count = Monster.count + 1
# Should be 2, because we created two monsters so far.
p Monster.count
# Predator hits Alien with Plasma.
alien.power = 75
p alien.power
# Alien heals to 90%.
alien.power = 90
p alien.power
if predator.power < alien.power
p 'Alien wins!'
elsif alien.power < predator.power
p 'Predator wins!'
else
p 'Alien and Predator tied...'
end
2
75
90
"Predator wins!"
Note
The code above is not professional and elegant, and there are much better ways to do that (like incrementing
@@countautomatically every time a new monster is created), but it serves as an example to show syntax and explain concepts.
Having talked about IVARs and CVARs, we now talk about CIVARs.
Class Instance Variables
Let’s review:
-
IVARS are declared inside instance methods or inside the special
initialize(constructor) method. -
CVARS are declared inside the body of the class (but can also be declared inside methods as long as they are defined with the
@@var_namesyntax) and need class methods to be manipulated.
Ruby also has class instance variables, which belong to the class instance.
class Thing
# A Class Instance Variable.
@num = 10
# A method getter/reader for the @num CIVAR.
def self.num
@num
end
# A method setter/writer for the @num CIVAR.
def self.num=(arg)
@num = arg
end
end
# Uses the CIVAR reader.
p Thing.num
# Uses the CIVAR writer.
Thing.num = 23
# Uses the CIVAR reader again.
p Thing.num
10
23
Note that to create methods that can manipulate CIVARs, the syntax is the same as for methods for CVARs. Another quick example:
class Person
# CVAR.
@@count = 0
#CIVAR.
@people = []
def initialize(name)
# IVAR.
@name = name
end
# An instance method getter/reader for @name IVAR.
def name
@name
end
# An instance method setter/writer for @name IVAR.
def name=(name)
@name = name
end
# A class method getter/reader for @@count CVAR.
def self.count
@@count
end
# A class method setter/writer for @@count CVAR.
def self.count=(num)
@@count = num
end
# A class instance method getter/reader for @people CIVAR array.
def self.people
@people
end
# A class instance method setter/writer for @people CIVAR array.
def self.people=(person)
# Appends a person object to the end of the array.
@people << person
end
end
# Creates an instance of Person
# Create person 1.
p1 = Person.new('Linus Torvalds')
# Increment the counter.
Person.count = Person.count + 1
# Store person 1 in the CIVAR on Person.
Person.people = p1
# Create person 2.
p2 = Person.new('John Carmack')
# Increment the counter.
Person.count = Person.count + 1
# Store person 2 in the CIVAR on Person.
Person.people = p2
p p1.name
p p2.name
p Person.count
p Person.people
"Linus Torvalds"
"John Carmack"
2
[#<Person:0x00000001dac838 @name="Linus Torvalds">, #<Person:0x00000001dac748 @name="John Carmack">]
TODO: We’ll see more (and hopefully useful) examples on how, when and why to use each of these types of variables later on.
Classes are Objects (again)
Recall that a class is itself an object - an instance of the Class class. That is, Thing is an instance of Class. If Class is an object, we can do Class.new, right? Sure! The syntax:
class Thing
# body...
end
is the same thing as:
Thing = Class.new do
# body...
end
We just pass a block to Class.new to define the body of the class.
Just as we can do Thing.new to create an instance of Thing, we can do Class.new to create an instance of Class and assign that instance to a variable name like Thing, just that Ruby provides the class Thing syntax sugar.
Alternative Syntax for Defining Class Instance Methods
Before we delve into the next topic, let me ask you something, just between you and me :) Remember that we used attr_reader, attr_writer, and attr_accessor earlier? You can use them as well to declare class instance methods (but not class methods).
class Person
@@count = 3 # CVAR
@count = 5 # CIVAR
# Create reader/writer for @count (CIVAR), not @@count (CVAR).
class << self
attr_reader :count
attr_writer :count
# or simply use attr_accessor :count.
end
# Let's create a getter/reader for @@count using a different name,
# otherwise we would be overriding the class instance reader method for the
# @count CIVAR.
def self.c_count
@@count
end
# Let's create a setter/writer for @@count using a different name,
# otherwise we would be overriding the class instance method writer for the
# @count CIVAR.
def self.c_count=(val)
@@count = val
end
end
p Person.count # 3 or 5? 5
Person.count = 6
p Person.count # 6
p Person.c_count = 3 # 3 because we never touched it after declaring it.
5
6
3
Since we use a symbol (:count) when defining the accessors, Ruby has to decide whether :count refers to the CVAR or the CIVAR, and it goes for the CIVAR. Therefore:
class << self
attr_accessor :foo # Always @foo CIVAR, never @@foo CVAR.
end
The syntax above CANNOT be used to create accessors to CVARS. For those, you need to use the def self.cvar_method syntax.
Inheritance
IVARs, CVARs and CIVARs behave differently under inheritance. Let’s see concepts and examples.
First of all, the syntax for defining inheritance is this:
class MySubClass < MySuperClass
# body...
end
Inheritance and Instance Variables
“An example is worth a thousand words.” - Fernando Basso!
class Enemy
attr_accessor :name, :power
def initialize(name)
@name = name
@power = 100
end
end
class Human < Enemy
end
class Robot < Enemy
end
human1 = Human.new('Mr. President')
robot1 = Robot.new('Megatron')
p human1.name
p robot1.name
"Mr. President"
"Megatron"
@name and @power are both defined in the Enemy superclass. Since our Human and Robot classes inherit from the Enemy class, they inherit those IVARs. Now pay attention: we are talking about IVARs here. It means @name in one object is different than @name in the other object, that is, if we change @name in human1, @name is robot1 is unchanged.
human1.name = 'Lex Luthor'
p human1.name
p robot1.name
"Lex Luthor"
"Megatron"
In short, each object has its own instance variable that is unrelated to instance variables from other objects created from a given class.
Inheritance and Class Variables
Class variables are inherited/shared between a superclass and its subclasses.
class Enemy
# A CVAR to keep track of how many instances of Enemy are created.
# Creating an instance of a sublcass of Enemy also increments this @@count.
@@count = 0
def initialize
# Every time an Enemy (or a subclass of Enemy) is instantiated,
# we increment the @@count class variable to keep track of how
# many enemies have been created.
self.class.increment_count # Invoke the method.
end
def self.increment_count
@@count += 1
end
# @@count doesn't need a setter/reader because we'll never update it from
# outside the class. We increment it automatically each time an enemy
# is created, and therefore need only a getter/reader.
def self.instances_count
@@count
end
end
class Human < Enemy
def initialize
super
end
end
class Robot < Enemy
def initialize
super
# Just as a test, let's increment by 1000 to prove that CVARs
# are shared between super and sub classes.
@@count += 1000
end
end
human1 = Human.new
human2 = Human.new
robot1 = Robot.new # This one will add 1000 more, remember?!
p Enemy.instances_count
1003
Inside the initialize method of Enemy, we see this line:
self.class.increment_count
increment_count is a class method that increments the CVAR @@count. But why do wee need to use class in that line? Because self is used inside the initialize method, which means self in that context points to the instance object, the instance of Enemy (or one of its subclasses). The object can’t access a class variable, but the class that gave birth to the object can indeed access the class variable. Then, from the object (self), we invoke the method class, which returns the class from which the object was constructed, and then, from the class, we invoke the class method increment_count.
def initialize
self.class.increment_count
end
self points to an object, lets say, human1. What is human1.class? It is Human. Therefore:
self.class.increment_count
is the same as
Human.increment_count
which because of inheritance is the same as
Enemy.increment_count
Inside each subclass, we created an initialize method that just invokes super causing the initialize method in the superclass to run, effectively incrementing @@count each time Human or Robot are instantiated.
This stuff is not easy to grasp due to the fact that there are several things involved at once. Just read it several times, run examples and tests, and even look for other tutorials that explain the same thing in different ways. Don’t be worried if it takes several study sessions to make sense out of this.
With the above examples, we showed how you could keep track of all enemies created. What about if we wanted to keep track of all enemies, but also keep a separate count for each specific type of enemies, like:
-
5 total enemies, from which:
-
3 are humans.
-
2 are robots
Several approaches are possible, but let’s take this opportunity to talk about how class instance variables behave when inheritance comes into play.
Inheritance and Class Instance Variables
Okay. Too keep track of all enemies, we used a @@count class variable in the superclass Enemy. Let’s use class instance variables to keep track of how many individual humans or robots are created as well.
class Enemy
# Keeps track of all instances, incuding instances from the subclasses.
@@count = 0
# Keeps track only of instances created with Enemy.new.
@count = 0
def initialize
# Increments @@count
self.class.increment_total_count
# Increments @count
self.class.increment_individual_count
end
def self.increment_total_count
@@count += 1
end
def self.total_instances_count
@@count
end
def self.increment_individual_count
@count += 1
end
def self.individual_instances_count
@count
end
end
class Human < Enemy
# Keeps track of instances created with Human.new.
@count = 0
def initialize
super
end
end
class Robot < Enemy
# Keeps track of instances created with Robot.new.
@count = 0
def initialize
super
end
end
human1 = Human.new
human2 = Human.new
robot1 = Robot.new # This one will add 1000 more, remember?!
# This time, let's also create an instance of Enemy itself.
enemy = Enemy.new
p Enemy.total_instances_count # 3
p Human.individual_instances_count # 2
p Robot.individual_instances_count # 1
p Enemy.individual_instances_count # 1
Here we see that each class, be it the superclass or a subclass must have their on individual @count variable, which is a class instance variable. Think about it. An instance variable belongs to an instance of a class (an object) A class variable belongs to the class (and its subclasses), and a class instance variable belongs to a class object (because classes are objects). That is, a CIVAR belongs to an object, just that in this case the object is a class. I know it is confusing. Read, re-read, practice, run tests, read other tutorials. It will eventually all make sense.
Quick Review
A normal method:
def foo
# operates on @ivar.
end
A method that can be either a class method to work on a CVAR or a class instance method to operate on a CIVAR (remember that we use self):
def self.foo
# operates on a @@cvar, or
# operates on a @civar
def
Invoke a class method or a class instance method from inside another method (remember that we use self.class.method_name):
def initialize
self.class.some_class_method
# or
self.class.some_class_instance_method
end
One could argue that there is no such things are class methods and class instance methods, but I used those terms mainly to denote the idea that the method will either work on a @cvar or on a @civar. The syntax for both is the same. More than that, the same method could work on both types of variables:
def self.do_something
@civar += 1
@@cvar += 1
end
Visibility (again)
All methods used in the previous examples are public because by default methods are public.
This:
class Foo
def hello
'Hi there!'
end
end
Is effectively the same as this:
class Foo
public
def foo
'Hi there!'
end
end
To make a method private, just change that public with private:
class Foo
# A public method.
def greet
hello # invoke the hello private method.
end
private # From this point on, all methods are private.
def hello
'Hi there!'
end
end
foo = Foo.new
p foo.greet
"Hi there!"
But now that method can’t be called from outside the class, only from inside (as we did). That is, this won’t work:
foo = Foo.new
p foo.hello
# error about trying to access private method.
It is possible to make class methods and class instance methods private as well (just that they are not as useful, because the main purpose of having these types of methods is exactly so that they can be invoked from the class name itself):
class Thing
# Let's start these with different values so it is easier
# to understand the different when we look at the results.
@@cvar = 10
@civar = 20
def initialize
# Invokes the private incr method.
self.class.incr
end
# A public getter/reader for our @@cvar.
def self.get_cvar
@@cvar
end
# A public getter/reader for our @civar.
def self.get_civar
@civar
end
private
# We can only increment from inside the class.
def self.incr
@@cvar += 1
@civar += 1
end
end
5.times { Thing.new }
p Thing.get_cvar
p Thing.get_civar
15
25