DEV Community 👩‍💻👨‍💻

Jose Aguilar
Jose Aguilar

Posted on

The basics of Object-Oriented Programming in Ruby

In this post I will be going over Object-Oriented Programming (OOP) and object-orientation in Ruby. If Ruby is your first OOP you may have come across this concept and perhaps found it a little tricky, well you're not alone! We'll go over what object-oriented programming is and its application in the Ruby language.

What is Object-Oriented Programming?

Wikipedia defines OOP as follows

Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which can contain data and code: data in the form of fields (often known as attributes or properties), and code, in the form of procedures (often known as methods).

In other words, the underlying concept of OOP is that everything is an object. Objects, as we know, contain data in the form of attributes (also known as properties) and functions that run some code (also known as methods). The most common form of OOP is class-based. In class-based OOP, objects are defined via classes, which act as an abstract, reusable template (or blueprint) for instances (objects) of that class. For example, an object(aka instance) named bob could belong to a class named Person, or an object named dog could belong to a class named Animal, and so on. A class can also define the names of attributes and/or methods an instance of said class should have, the instance will then inherit the attributes and methods of the parent class when it's created.

Let's look at an example:

Source: https://www.educative.io/blog/object-oriented-programming

In the above example, there is a class (Car) that contains attributes representing the color, brand, and model of a Car object. We can then instantiate 2 objects from the class Car (myCar and helensCar) that will inherit the attributes and methods of its parent class (Car).

Here's an example of what this looks like in Ruby:

#Classes in Ruby and other OOP languages follow the camel case rule; names must be uppercase and no spaces are allowed.
class Car
  def initialize(color, brand, model)
    @color = color
    @brand = brand
    @model = model
  end

#Instance methods are declared in the parent class and are available across all instances of the parent class.
  def repaint(new_color)
    @color = new_color
  end
end

#Instantiates a new object (charger) with the .new method
charger = Car.new('red', 'Dodge', 'Charger')

p charger 
# => #<Car:0x00000000012973d0 @color="red", @brand="Dodge", @model="Charger">

charger.repaint('blue')

p charger 
# => #<Car:0x00000000012973d0 @color="blue", @brand="Dodge", @model="Charger">
Enter fullscreen mode Exit fullscreen mode

An object is instantiated by calling the .new method on a class followed by the arguments that will become the instance variables (attributes) of that instance. Any methods declared in the parent class will automatically be inherited. In order to create an object with the instance variables of the parent class, a special method is needed: initialize.

The Initialize method and instance variables/methods.

When instantiating an object, more often than not you will be passing one or more arguments to create instance variables for that object. In order to accomplish this, you will first need to declare a special Ruby method in the parent class, called initialize.

Lets look at that method from the previous example:

def initialize(color, brand, model)
    @color = color
    @brand = brand
    @model = model
end
Enter fullscreen mode Exit fullscreen mode

If you've come across object-orientation in other languages, such as JavaScript, this is also known as a constructor method. It is a special method that is used for creating and initializing an instance object of a class.

You'll notice that within the method are variables being declared with a leading @ character. The @ character symbolizes an instance variable. Instance variables are variables that are available across all instances of a class. In the previous example, every object that's instantiated from the Car class with have the variables @color, @brand, and @model with their own given values. The repaint method is an instance method, and thus will also be available to all instances of that class.

Getter/Setter methods

In order to directly access object variables from outside of the class, i.e. to be able to call charger.color and have it return red, we need to implement getter methods. A getter method is simply a method that allows you to read the value of an instance variable.


class Car

  #...

  #Getter methods defined below
  def color
    @color
  end

  def brand
    @brand
  end 

  def model
    @model
  end
end

charger = Car.new('red', 'Dodge', 'Charger')

p charger.color 
# => "red"

p charger.brand 
# => "Dodge"

p charger.model 
# => "Charger"
Enter fullscreen mode Exit fullscreen mode

We defined 3 getter methods named after an instance variable and simply had them return the value of that variable. Pretty straightforward!

If we didn't have a getter method for an instance variable, we would get an error when trying to read the variable:

charger.color 
# => undefined method `color' for #<Car:0x00000000012973d0> (NoMethodError)
Enter fullscreen mode Exit fullscreen mode

Now what if we wanted to change the value of a variable? Since we can now access variables directly using getter methods, we should just as easily be able to change the values of said variables... right?

charger.color = 'blue'
# => undefined method `color=' for #<Car:0x0000000002616668> (NoMethodError)
#Did you mean?  color
Enter fullscreen mode Exit fullscreen mode

Notice the part that reads undefined method 'color=', then the part that reads Did you mean? color. color is an already established getter method within our class, however color= is not currently an existing method, hence the error.

The trailing = character in a method name (such as color=) designates a method as a setter method, therefore color and color= are two totally separate methods.

Setter methods are set up like so:

class Car

#...

 #Setter methods defined below
  def color=(color)
    @color = color
  end

  def brand=(brand)
    @brand = brand
  end 

  def model=(model)
    @model = model
  end
end
Enter fullscreen mode Exit fullscreen mode

Now, we're able to both read variable values as well as change them.

p charger.color
# => "red"

charger.color = 'blue'
p charger.color
# => "blue"
Enter fullscreen mode Exit fullscreen mode

A little factoid: Since = symbolizes a setter method. We can also call our method like charger.color=(blue) and get the same result. Thankfully, thanks to syntax sugar we are able to write it as charger.color = 'blue' for better readability.

Accessor methods

Now if you're thinking that having to write getters and setters for every single variable is a lot of work, you thought right. A cornerstone of coding is the DRY principle, and writing repetitive code is not only time consuming but ugly to look at.

Thankfully, Ruby gives us an easy way to set up our getters and setters in one line of code! Enter accessor methods.

There are 3 accessor methods available to make our lives easier:

  • attr_reader: For creating getter methods
  • attr_writer: For creating setter methods
  • attr_accessor: For creating both getter and setter methods at once!!

attr_accessor allows us to create both getters and setters in a single line of code. Let's refactor our code to use this method.

class Car
#Accessor methods
  attr_accessor :color, :brand, :model

  def initialize(color, brand, model)
    @color = color
    @brand = brand
    @model = model
  end

  def repaint(new_color)
    @color = new_color
  end
end
Enter fullscreen mode Exit fullscreen mode

We call attr_accessor and pass in our variable arguments as Symbols. This creates both attr_reader and attr_writer for each variable all in one line.. much better!

Everything is an object

Going back to the concept of everything is an object, does that mean that strings, arrays, numbers, etc are objects too? Yes, they are!

When you create an array or a string, you are creating an object from a built-in Ruby class!!

We can demonstrate this by calling the .class method, which shows us the class the object belongs to.

p [].class
# => Array

p "Hello World".class
# => String

p 123.class
# => Integer
Enter fullscreen mode Exit fullscreen mode

Arrays, strings, and numbers belong to the Array, String, and Integer classes respectively. These are some of the built-in classes within Ruby with their own methods and variables. Think about when you're using the .capitalize or .reverse method on a string, those are instance methods belonging to the String class!

Conclusion

These are some of the basics of object-oriented programming in Ruby. With this knowledge you're ready for other OOP concepts such as self, class variables, and class methods. Good luck and have fun!

Sources

Top comments (0)

16 Libraries You Should Know as a React Developer

>> Check out this classic DEV post <<