At the end of the last blog post called: Methods and Attributes in Object-oriented programming in Ruby we study about attributes. Let’s recap
Attributes (object information)
Just as an object has behavior (methods), objects can also have information or attributes. For example, a person can have a name, age, height, etc. These are the attributes. Think of attributes as variables that are associated with the object.
In Ruby, we will identify attributes in a class because they begin with the @ character. For example, we can store the argument coming from the constructor inside the Person attribute. Why is this important? Because once we have an instance variable in the constructor, we can use it in any other method. Let's see how
#1. Class declaration
class Person
#2. Constructor method called "initialize"
def initialize(name)
@name = name
end
# 3. Behavior or logic to apply in each instance
def greet
"Hello #{@name}!"
end
end
#4. Instantiation
p1 = Person.new("Ana")
p2 = Person.new("Daniel")
p3 = Person.new("Matz")
#5. Printing out and calling "greet" class method
puts p1.greet
puts p2
puts p3.greet
Let’s execute the code
➜ blog_tutorials ruby oop.rb
Hello Ana!
#<Person:0x0000000000a6eca8>
Hello Matz!
We have just said that instance variables declared as @ can be used in any other method inside the current class. But what if we want to use it in another class? Or outside the class? Here is where we can use the getters and setters
Getters and Setters
These methods allow us to access a class’s instance variable from outside the class. Getter methods are used to get the value of an instance variable while the setter methods are used to set the value of an instance variable of some class.
This is also called attributes visibility, because in our case the attribute @name can be just called inside Person instances, and if we want to expose it outside the class we have to create methods to read it or edit it.
I know this can be a bit confusing, actually is one of the hardest part to understand in Object-oriented world, so let’s see an example
Getter
Is the method we have to create inside the class to be read outside the class. It’s the asy one, because we need just to create a new method and return the instance variable
#1. Class declaration
class Person
#2. Constructor method called "initialize"
def initialize(name)
@name = name
end
# 3. Behavior or logic to apply in each instance
def greet
"Hello #{@name}!"
end
#4. Getter method. @name can be read from the outside
def name
@name
end
end
#5. Instantiation
p1 = Person.new("Ana")
p2 = Person.new("Daniel")
p3 = Person.new("Matz")
#6. Printing out and calling a class method in some of them
puts p1.greet
puts p2
puts p3.greet
#7. Getting the method from outside the class
p4 = Person.new("Mary")
puts p4.name
Let’s execute this code
➜ blog_tutorials ruby oop.rb
Hello Ana!
#<Person:0x000000000235b338>
Hello Matz!
Mary
As we learned previously, the instance variables (start with @) are made to be used inside the class, for other methods. However here we are printing out the instance variable outside the class. In the Step #4 We declared the method which returns the instance variable. In Step #7 We created a new object called “p4” and then we called the getter method called “name”.
A bit weird, I know. And probably seems useless at this moment, but believe me: is used a lot in Ruby OOP. For instance, what will happen if we have more instance variables in the class, for example: @age and @gender. What do you think we should do now?
We should do something like this:
#1. Class declaration
class Person
#2. Constructor method called "initialize"
def initialize(name, age, gender)
@name = name
@age = age
@gender = gender
end
# 3. Behavior or logic to apply in each instance
def greet
"Hello #{@name}!"
end
#4. Getter methods. @name, @age and @gender can be read from the outside
def name
@name
end
def age
@age
end
def gender
@gender
end
end
#5. Instantiation
p1 = Person.new("Ana", 25, "Female")
p2 = Person.new("Daniel", 29, "Male")
p3 = Person.new("Matz", 30, "Male")
#6. Printing out and calling a class method in some of them
puts p1.greet
puts p2
puts p3.greet
#7. Getting the method from outside the class
p4 = Person.new("Mary", 28, "Female")
puts p4.name, p4.age, p4.gender
Let’s execute the code
➜ blog_tutorials ruby oop.rb
Hello Ana!
#<Person:0x00000000027fef70>
Hello Matz!
Mary
28
Female
Here we have some interesting facts
- In Step #1 we changed the constructor (initializer) to receive and save 2 new instance variables @age and @gender
- In Step#4 we created other 2 methods, “age” and “gender” and each one of them returns the corresponding instance variable
- In Step#5 and Step#7 we create the objects with all the new instances (name, age, gender)
- In Step #7 at the final line we printed out the 3 instance variables for “Mary” who is the person assigned to “p4”
This is the way we can establish getters, but there is a better way. Let’s see this code.
#1. Class declaration
class Person
#2. Getter methods. @name, @age and @gender can be read from the outside
attr_reader :name, :age, :gender
#3. Constructor method called "initialize"
def initialize(name, age, gender)
@name = name
@age = age
@gender = gender
end
# 4. Behavior or logic to apply in each instance
def greet
"Hello #{@name}!"
end
end
#5. Instantiation
p1 = Person.new("Ana", 25, "Female")
p2 = Person.new("Daniel", 29, "Male")
p3 = Person.new("Matz", 30, "Male")
#6. Printing out and calling a class method in some of them
puts p1.greet
puts p2
puts p3.greet
#7. Getting the method from outside the class
p4 = Person.new("Mary", 28, "Female")
puts p4.name, p4.age, p4.gender
Let’s execute the code
➜ blog_tutorials ruby oop.rb
Hello Ana!
#<Person:0x0000000001dd30c8>
Hello Matz!
Mary
28
Female
Can you see something different?
We removed the Step#4 where we had the 3 methods who returned the instance variables. This is because Ruby has a special method to deal with getters and with that we don’t have to create a lot of methods each time we need an instance variable outside the class. The name of these methods are accessors. In our case the accessor is called attr_reader.
So, in Step#2 We declared the accessor method attr_reader with the 3 instance variables as “symbols”. Here you can learn a bit more about
Ruby Symbols. Then we deleted the 3 methods created previously in Step#4 and that's it. We have the same result as before, but with less code and better organized.
Setters
This is a bit more complex (in the beginning), but it works similar to getters. The difference with getters is that setters allow us to edit the instance variable from outside the class and we use a syntax quite different. Let’s see it
#1. Class declaration
class Person
#2. Getter methods. @name, @age and @gender can be read from the outside
attr_reader :name, :age, :gender
#3. Constructor method called "initialize"
def initialize(name, age, gender)
@name = name
@age = age
@gender = gender
end
# 4. Behavior or logic to apply in each instance
def greet
"Hello #{@name}!"
end
# 5. Setter for @name instance variable
def name=(name)
@name = name
end
end
#6. Instantiation
p1 = Person.new("Ana", 25, "Female")
p2 = Person.new("Daniel", 29, "Male")
p3 = Person.new("Matz", 30, "Male")
#7. Printing out and calling a class method in some of them
puts p1.greet
puts p2
puts p3.greet
#8. Getting the method from outside the class
p4 = Person.new("Mary", 28, "Female")
puts p4.name, p4.age, p4.gender
#9. Setting a new Name (editing instance variable)
p4.name=("Maria")
puts p4.name
Let’s print the result
➜ blog_tutorials ruby oop.rb
Hello Ana!
#<Person:0x000000000282ece8>
Hello Matz!
Mary
28
Female
Maria
As always, what can you see differently?
In Step#5 we have created a new method with a weird syntax
# 5. Setter for @name instance variable
def name=(name)
@name = name
end
This sintaxis means that we’re declaring a setter method, so we receive the param name and assign it to the original instance variable @name. Again, I know is confused, but let’s check the Step #8 and Step#9. First we instantiate Person as “Mary”. But in Step #9 We resigned that name to “Maria” just calling the method and passing the new parameter.
Maybe you are asking now, if we have an accessor for this, because if we want to edit age and gender, we have to create other 2 methods similar to above. Yes, ruby has the accessor method called attr_writer, let’s refactor and see how it looks
#1. Class declaration
class Person
#2. Getter methods. @name, @age and @gender can be read from the outside
attr_reader :name, :age, :gender
#3. Setter method for name
attr_writer :name
#4. Constructor method called "initialize"
def initialize(name, age, gender)
@name = name
@age = age
@gender = gender
end
# 5. Behavior or logic to apply in each instance
def greet
"Hello #{@name}!"
end
end
#6. Instantiation
p1 = Person.new("Ana", 25, "Female")
p2 = Person.new("Daniel", 29, "Male")
p3 = Person.new("Matz", 30, "Male")
#7. Printing out and calling a class method in some of them
puts p1.greet
puts p2
puts p3.greet
#8. Getting the method from outside the class
p4 = Person.new("Mary", 28, "Female")
puts p4.name, p4.age, p4.gender
#9. Setting a new Name (editing instance variable)
p4.name=("Maria")
puts p4.name
Let’s print out
➜ blog_tutorials ruby oop.rb
Hello Ana!
#<Person:0x00000000026a6e70>
Hello Matz!
Mary
28
Female
Maria
And we have the same result as before. We deleted the Step#5 because it contains the setter method, now we have the accessor method in Step #3 as attr_writer. Nice!
Accessors
But what happens if we want to have the ability to read and write all three instance variables? (name, age and gender). We have the third and last Ruby accessor method: attr_accessor. This accessor generates the automatic Getter & Setter method for the given item.
#1. Class declaration
class Person
#2. Getter methods. @name, @age and @gender can be read from the outside
attr_accessor :name, :age, :gender
#3. Constructor method called "initialize"
def initialize(name, age, gender)
@name = name
@age = age
@gender = gender
end
# 4. Behavior or logic to apply in each instance
def greet
"Hello #{@name}!"
end
end
#5. Instantiation
p1 = Person.new("Ana", 25, "Female")
p2 = Person.new("Daniel", 29, "Male")
p3 = Person.new("Matz", 30, "Male")
#6. Printing out and calling a class method in some of them
puts p1.greet
puts p2
puts p3.greet
#7. Getting the method from outside the class
p4 = Person.new("Mary", 28, "Female")
puts p4.name, p4.age, p4.gender
#8. Setting a new Name (editing instance variable)
p4.name=("Maria")
puts p4.name
Let’s print
➜ blog_tutorials ruby oop.rb
Hello Ana!
#<Person:0x00000000020f6e30>
Hello Matz!
Mary
28
Female
Maria
What we did here was to refactor Step#2 with the attr_accessor who generates the automatic Getter & Setter method for the given items. So now we read and edit the 3 instance variables from outside the class.
Remember, there are three types of accessors in Ruby
attr_reader : This accessor generates the automatic Getter method for the given item.
attr_writer : This accessor generates the automatic Setter method for the given item.
attr_accessor : This accessor generates the automatic Getter & Setter method for the given item.
Next blog post we’ll be doing some exercises to be more clear with Ruby objects and classes, and also with instance variables (getters and setters)
I hope you learned a lot with this post
Thanks for reading
Daniel Morales