Picture of myself

Why Classes?

January the 24th in year 2015

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:

=> Let's see some mean machines

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:

=> Is engine in the car? => true
=> How many wheels does it have? => 4

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:

=> test.rb:15: syntax error, unexpected end-of-input
obj.color=

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:

=> Is engine in the car? => true
=> How many wheels does it have? => 4
=> What is the colour? => blue

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:

=> Is engine in the car? => true
=> How many wheels does it have? => 4
=> What is the colour? => blue

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:

=> Is engine in the car? => true
=> How many wheels does it have? => 4
=> What is the colour? => blue

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:

=> ./blogclass.rb:10:in `display': undefined local variable
or method `engine' for #<Car:0x00000002c344a0> (NameError)
from ./blogclass.rb:18:in `<main>'

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:

=> Is engine in the car? => true
=> How many wheels does it have? => 4
=> What is the colour? => blue

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:

=> "Audi"
=> "A4"
=> Is engine in the car? => true
=> How many wheels does it have? => 4
=> What is the colour? => blue

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:

=> "Audi"
=> "A4"
=> Is engine in the car? => true
=> How many wheels does it have? => 4
=> What is the colour? => blue

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:

=> "Audi"
=> "A4"
=> Is engine in the car? => true
=> How many wheels does it have? => 4
=> What is the colour? => blue

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:

=> "Audi"
=> "A4"
=> Is engine in the car? => true
=> How many wheels does it have? => 4
=> What is the colour? => blue

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:

=> Is engine in the car? => true
=> How many wheels does it have? => 4
=> What is the colour? blue
=> Which brand is it? => Audi
=> And what model? => A4
=>
=> Is engine in the car? => true
=> How many wheels does it have? => 4
=> What is the colour? black
=> Which brand is it? => VW
=> And what model? => Passat
=>
=> Is engine in the car? => true
=> How many wheels does it have? => 4
=> What is the colour? red
=> Which brand is it? => Ford
=> And what model? => Mustang

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.

...by Marko Anton Potocnik