Classes. Why do we even need classes?
Well, they can be really useful when writing your code, because you don't need to rewrite it when dealing with similar objects. Let's say for example, I need to create a few cars in my code. I'd go:
car_1_make = 'Audi'
car_1_model = 'A4'
car_1_colour = 'blue'
car_1_engine = true
car_1_number_of_wheels = 4
And then I'd start with another:
car_2_make = 'VW'
car_2_model = 'Passat'
car_2_colour = 'black'
car_2_engine = true
car_2_number_of_wheels = 4
And another:
car_3_make = 'Ford'
car_3_model = 'Mustang'
car_3_colour = 'red'
car_3_engine = true
car_3_number_of_wheels = 4
Then I'd have to type something that my program will actually do for each car individually. It makes a mess with of all the variables, and there's so much repetition the head starts to hurt. So what we need is something that would keep this information for us.
Let's look at a simple class:
class Car
def initialize
puts "Let's see some mean machines"
end
end
We added a method inside called initialize which means that whenever we make an instance of this class, it will be executed.
So let's do that:
obj = Car.new
It's that simple.
Now we have an object obj which is an instance of Car class (meaning it plays by the rules written in class Car), and since we said puts "Let's see some mean machines"
the output of the code will be:
But this is not what we are after. We need to make some cars! If we look at our variables defined at the beginning, we see all of our cars have engine and 4 wheels. That means we could put this information in initialize method, because it will always be the same with all the cars.
So let's do that:
class Car
def initialize
engine = true
number_of_wheels = 4
end
end
Awesome! Now let's try to test the code. Before we'll do that, we should enter some puts
just for testing reasons, so we'll see what's going on.
class Car
def initialize
engine = true
number_of_wheels = 4
puts "Is engine in the car? => #{engine}"
puts "How many wheels does it have? => #{number_of_wheels}"
end
end
We create an instance like before:
obj = Car.new
And our output is:
Yeey!!! We're doing great!
So we have a car that has an engine and 4 wheels, but what about the rest of the car? This is something that isn't the same with all the cars, so we have to be able to access it an change it. For that we'll need to add new methods in our class. One method that runs once, just when class initializes won't make it. We need more! One thing to mention is, that it is very common to name the methods in classes so they end with equal sign (=). Why is this good? We'll see a bit later, but let's just remember to do that when we make them.
First we'll add the colour of the car, just to see how this goes first.
class Car
def initialize
engine = true
number_of_wheels = 4
puts engine
puts number_of_wheels
end
def colour=
colour = 'blue'
puts colour
end
end
We added another method, named it as it should be named, and added some value blue
to the variable colour
. Now we need to call it, but because only initialize method runs when object of the class obj
is created, we need to call it separately on the object, like so:
obj = Car.new
obj.colour=
And we get an error:
It's saying we can't end the line with '='.
Ok let's remove it:
class Car
def initialize
engine = true
number_of_wheels = 4
puts "Is engine in the car? => #{engine}"
puts "How many wheels does it have? => #{number_of_wheels}"
end
def colour
colour = 'blue'
puts "What is the colour? #{colour}"
end
end
obj = Car.new
obj.colour
And the output is exactly what we want:
But we want to change the colour, not have it hardcoded like here. We need to put the colour in when we call the method. First we need to change our method in the class so it will take some arguments like 'colour_argument' and then we'll assign the value when we'll call it.
class Car
def initialize
engine = true
number_of_wheels = 4
puts "Is engine in the car? => #{engine}"
puts "How many wheels does it have? => #{number_of_wheels}"
end
def colour(colour_argument)
colour = colour_argument
puts "What is the colour? #{colour}"
end
end
obj = Car.new
obj.colour('blue')
Output:
Great! We got the same output as before, but we defined the colour when calling the method.
Awesome!
Now, let's return to that error we were given. Why was it raised, if we were suppose to put '=' at the end. OK, let's try it again, now that we have one argument and let's see what happens:
class Car
def initialize
engine = true
number_of_wheels = 4
puts "Is engine in the car? => #{engine}"
puts "How many wheels does it have? => #{number_of_wheels}"
end
def colour=(colour_argument)
colour = colour_argument
puts "What is the colour? #{colour}"
end
end
obj = Car.new
obj.colour = 'blue'
Output:
Alright!!!
Same output but look how much better and logical the call to the method looks like. It's almost like assigning the value to the method, where we know it's actually giving it some parameters, but it looks like so, and we love it!
There's something I don't like about this class. It prints the attributes whenever I assign them. I want it to tell me when I want, and be quiet when I deal with the data. Let's create a method where we'll put all the puts
.
class Car
def initialize
engine = true
number_of_wheels = 4
end
def colour=(colour_argument)
colour = colour_argument
end
def display
puts "Is engine in the car? => #{engine}"
puts "How many wheels does it have? => #{number_of_wheels}"
puts "What is the colour? #{colour}"
end
end
We didn't call it display=
because it doesn't take any arguments, so it has to be called just display
. Now we need to add another method call to our obj
because we got a new method. Here we go:
obj = Car.new
obj.colour = 'blue'
obj.display
Output:
Wait what?!?! Our engine variable doesn't exist, but it worked just fine before. Hmm?
I guess this has to do something with the fact that it is a local variable, which means it is only recognisable within that particular method. What we need is a variable which would be much more famous and be known all over the class. As it turns out, ruby offers a great solution for that, and it has something to do with emails! Yes, that's right! It's the @ character. If we put @ in front of all of our variables we call in display
we would rename them from local variable to instance variable. Let's do it:
class Car
def initialize
@engine = true
@number_of_wheels = 4
end
def colour=(colour_argument)
@colour = colour_argument
end
def display
puts "Is engine in the car? => #{@engine}"
puts "How many wheels does it have? => #{@number_of_wheels}"
puts "What is the colour? #{@colour}"
end
end
obj = Car.new
obj.colour = 'blue'
obj.display
Output:
YESSSSS!!! We rock!
Now let's add all the rest of the features of the car, like make and model. We'll need to create a method for each of them, just like we did for colour.
class Car
def initialize
@engine = true
@number_of_wheels = 4
end
def colour=(colour_argument)
@colour = colour_argument
end
def make=(make)
@make = make
end
def model=(model)
@model = model
end
def display
puts "Is engine in the car? => #{@engine}"
puts "How many wheels does it have? => #{@number_of_wheels}"
puts "What is the colour? #{@colour}"
end
end
obj = Car.new
obj.colour = 'blue'
obj.make = 'Audi'
obj.model = 'A4'
obj.display
Now we can finally give some value to the rest of our car, but the output would still be the same, so we need to create a method that would return the value we just added. It's a very simple method.
class Car
def initialize
@engine = true
@number_of_wheels = 4
end
def colour=(colour_argument)
@colour = colour_argument
end
def make=(make)
@make = make
end
def make
@make
end
def model=(model)
@model = model
end
def model
@model
end
def display
puts "Is engine in the car? => #{@engine}"
puts "How many wheels does it have? => #{@number_of_wheels}"
puts "What is the colour? #{@colour}"
end
end
obj = Car.new
obj.colour = 'blue'
obj.make = 'Audi'
p obj.make
obj.model = 'A4'
p obj.model
obj.display
Output:
Pay attention to the fact that we could easily name the new methods which return the value make
because the other one, which gathers value is called make=
. Look as well at the test code, at the bottom where we call it. We added the p
so it prints the output. It's easier like that then to change the code in the class.
Everything goes so smoothly, I'm so excited!
If we look at the code, we see that it has a lot of repetition, and if you repeat yourself, people will not want to talk to you, because they'll be embarrassed to be seen in your company, so you better make sure your code is DRY (Don't Repeat Yourself).
So how to make this shorter? I'll tell you a secret that everybody who codes in ruby knows, so you better pay attention!
attr_reader
attr_writer
attr_accessor
These three can do some real magic. Let's implement one at the time:
attr_reader replaces the the method that returns our value. This is the last method we implemented. Let's replace the one for the make
, with attr_reader :make
.
class Car
attr_reader :make
def initialize
@engine = true
@number_of_wheels = 4
end
def colour=(colour_argument)
@colour = colour_argument
end
def make=(make)
@make = make
end
def model=(model)
@model = model
end
def model
@model
end
def display
puts "Is engine in the car? => #{@engine}"
puts "How many wheels does it have? => #{@number_of_wheels}"
puts "What is the colour? #{@colour}"
end
end
obj = Car.new
obj.colour = 'blue'
obj.make = 'Audi'
p obj.make
obj.model = 'A4'
p obj.model
obj.display
Output:
See? It works! And it's so much shorter to write. All you have to do is to add a symbol (it's name starting with ':') so the computer knows how to name it.
Let's continue to the next one, attr_writer. Let's do it to the colour. It's the writer method that has been with us since the beginning, but now the time has come to update it to something more modern. Let's change it to:
attr_writer :colour
.
class Car
attr_reader :make
attr_writer :colour
def initialize
@engine = true
@number_of_wheels = 4
end
def make=(make)
@make = make
end
def model=(model)
@model = model
end
def model
@model
end
def display
puts "Is engine in the car? => #{@engine}"
puts "How many wheels does it have? => #{@number_of_wheels}"
puts "What is the colour? #{@colour}"
end
end
obj = Car.new
obj.colour = 'blue'
obj.make = 'Audi'
p obj.make
obj.model = 'A4'
p obj.model
obj.display
Output:
Ahh. It's getting easier and easier to breath here. Let's finish with the last one, attr_accessor. For the previous two the name is very clear, and so is for this one. It combines both of the previous shortcuts into one super cool one. This means it does both reads and writes in one simple line. Let's do it to model, get rid of both methods, and use attr_accessor :model
instead.
class Car
attr_reader :make
attr_writer :colour
attr_accessor :model
def initialize
@engine = true
@number_of_wheels = 4
end
def make=(make)
@make = make
end
def display
puts "Is engine in the car? => #{@engine}"
puts "How many wheels does it have? => #{@number_of_wheels}"
puts "What is the colour? #{@colour}"
end
end
obj = Car.new
obj.colour = 'blue'
obj.make = 'Audi'
p obj.make
obj.model = 'A4'
p obj.model
obj.display
Output:
And this is how it's done!
Let's make our code even cooler. Let's put make
in attr_accessor
and add make display
method do it's job for everybody And finally, let's call the methods in a nicer manner.
class Car
attr_accessor :make
attr_writer :colour
attr_accessor :model
def initialize
@engine = true
@number_of_wheels = 4
end
def display
puts "Is engine in the car? => #{@engine}"
puts "How many wheels does it have? => #{@number_of_wheels}"
puts "What is the colour? #{@colour}"
puts "Which brand is it? => #{@make}"
puts "And what model? => #{@model}"
end
end
car1 = Car.new
car1.colour = 'blue'
car1.make = 'Audi'
car1.model = 'A4'
car1.display
puts
car2 = Car.new
car2.colour = 'black'
car2.make = 'VW'
car2.model = 'Passat'
car2.display
puts
car3 = Car.new
car3.colour = 'red'
car3.make = 'Ford'
car3.model = 'Mustang'
car3.display
Output:
Now imagine you have this class in another file, and you just type the color, make and model, and it will print the whole car, just like that. Almost like magic.
So in case you still wondered, this is why we need classes.