Introduction to Object-Oriented Programming with Ruby
Tags: ruby, oop, tutorial, beginners
Published: 2019-02-26 22:10:51

---
title: Introduction to Object-Oriented Programming with Ruby
published: true
description: Learn the fundamental concepts of OOP in Ruby by building an API connector to send an SMS!
tags: ruby, oop, tutorial, beginners
series: Next XYZ Courses
cover_image: https://thepracticaldev.s3.amazonaws.com/i/39d5cpfyd575km3i1itp.png
canonical_url: https://next.tech/blog/introduction-to-object-oriented-programming-with-ruby
---

**Object-oriented programming** (**OOP**) is a programming paradigm organized around objects. At a high level, OOP is all about being able to structure code so that its functionality can be shared throughout the application. If done properly, OOP can lead to very elegantly written programs that have minimal code duplication.

This is opposed to procedural programming (PP), in which you build programs in sequential order and call methods when you want shared behavior between pages in the application. Common procedural programming languages include [C](https://en.wikipedia.org/wiki/C_%28programming_language%29) and [Go](https://en.wikipedia.org/wiki/Go_%28programming_language%29).

In this tutorial, you’ll learn the fundamental concepts of OOP for **Ruby**, an object-oriented programming language wherein everything is an object. We will be using Ruby since one of its defining attributes — in addition to its elegant syntax and readability — is how it implements OOP techniques. This makes it a great language to start learning OOP with.

We will cover:

- Creating classes
- Instantiating objects
- Initializing arguments
- Working with inheritance, and
- Private and public methods.

In learning these concept, we will build out our own application: an API connector that communicates dynamically with an application that sends a text message. This will include walking through how to leverage concepts such as inheritance and object instantiation to make our code more scalable and reusable.

#### This brief tutorial is adapted from Next Tech’s [Introduction to Ruby course](https://c.next.tech/2EjW6vY), which includes an in-browser sandboxed environment and auto-checked interactive tasks to complete.

------

## Creating Classes

Before we begin, let’s define what an **object** is. At its core, an object is a self-contained piece of code that contains data (“attributes”) and behavior (“methods”) and can communicate with other objects. Objects of the same type are created from **classes**, which act as blueprints that define properties and behavior.

Creating a class in Ruby is fairly easy. To define a class, simply type the `class` word followed by the name of the class, and end it with the `end` word. Anything contained between `class` and `end` belongs to this class.

Class names in Ruby have a very specific style requirement. They need to start with a letter and if they represent multiple words, each new word needs also to be an uppercase letter — i.e. “CamelCase”.

We’ll start by creating a class called `ApiConnector`:

```ruby
class ApiConnector
end
```

Classes in Ruby can store both data and methods. In many traditional OOP languages such as Java, you need to create two methods for each data element you want to be included in the class. One method, the **setter**, sets the value in the class. The other method, the **getter**, allows you to retrieve the value.

The process of creating setter and getter methods for every data attribute can be tiresome and leads to incredibly long class definitions. Thankfully Ruby has a set of tools called **attribute accessors**.

Let’s implement some setters and getters for some new data elements for our class. Since it’s an API connector, it would make sense to have data elements such as `title`, `description`, and `url`. We can add these elements with the following code:

```ruby
class ApiConnector
  attr_accessor :title, :description, :url
end
```

When you merely create a class, it doesn't do anything — it is simply a definition. In order to work with the class, we need to create an instance of it…we’ll cover that next!

## Instantiation

To understand what **instantiation** is, let’s consider a real-world analogy. Let’s imagine that you’re building a house. The first task is to build a blueprint for the house. This blueprint would contain attributes and features of the house, such as the dimensions for each room, how the plumbing will flow, and so on.

Is the blueprint of the house the actual house? Of course not, it simply lists out the attributes and design elements for how the home will be created. So after the blueprint is completed, the actual home can be built — or, “instantiated”.

As explained in the previous section, in OOP, a class is the blueprint for an object. It simply describes what an object will look like and how it will behave. Therefore, instantiation is the process of taking a class definition and creating an object that you can use in a program.

Let’s create a new instance of our `ApiConnector` class and store it in a variable called `api`:

```ruby
api = ApiConnector.new
```

Now that we have an object created, we can use the `api` variable to work with the class attributes. For example, we can run the code:

```ruby
api.url = "https://next.tech/"
p api.url
```

```
[Out:]
https://next.tech
```

In addition to creating attributes, you can also create methods within a class:

```ruby
def test_method
  p "testing class call"
end
```

To access this method, we can use the same syntax that we utilized with the attribute accessors:

```ruby
api.test_method
```

Putting this altogether, running the full class code below will result in the `url` and the `test_method` message to be printed:

```ruby
class ApiConnector
  attr_accessor :title , :description , :url

  def test_method
    p  "testing class call"
  end
end

api =  ApiConnector.new

api.url = "https://next.tech/"
p api.url

api.test_method`
```

```
[Out:]
"https://next.tech"
"testing class call"
```

## Initializer Method

One thing you may find handy in Ruby development is the ability to create an **initializer** method. This is simply a method called `initialize` that will run every time when you create an instance of your class. In this method, you can give values to your variables, call other methods, and do just about anything that you think should happen when a new instance of that class is created.

Let’s update our `ApiConnector` to utilize an initializer method:

```ruby
class ApiConnector
  def initialize(title, description, url)
    @title = title
    @description = description
    @url = url
  end
end
```

Within the `initialize` method, we created an instance variable for each of the parameters so that we can use these variables in other parts of the application as well.

We also removed the `attr_accessor` method since the new `initialize` method will take care of this for us. If you need the ability to call the data elements outside of the class, then you would still need to have the `attr_accessor` call in place.

To test if the `initialize` method is working, let’s create another method within the class that prints these values out:

```ruby
def testing_initializer
  p @title
  p @description
  p @url
end
```

Finally, we’ll instantiate the class and test the initialize method:

```ruby
api = ApiConnector.new("My title", "My cool description", "https://next.tech")
api.testing_initializer
```

```
[Out:]
"My title"
"My cool description"
"https://next.tech"
```

### Working with optional values

Now, what happens when we want to make one of these values optional? For example, what if we want to give a default value to the URL? To do that, we can update our `initialize` method with the following syntax:

```ruby
def initialize(title, description, url = "https://next.tech")
```

Now our program will have the same output even if we don’t pass the `url` value while creating a new instance of the class:

```ruby
api = ApiConnector.new("My title", "My cool description")
```

### Using named arguments

Though this looks simple, passing arguments can get complex in real-world Ruby applications because some methods may take a large number of arguments. In such cases, it becomes difficult to know the order of arguments and what values to assign to them.

To avoid this confusion, you can utilize named arguments, like this:

```ruby
class ApiConnector
  def initialize(title:, description:, url: "https://next.tech")
    ...
  end
  ...
end

api = ApiConnector.new(title: "My title", description: "My cool description")
api.testing_initializer 
```

You can enter the arguments without having to look at the order in the `initialize` method, and even change the order of the arguments without causing an error:

```ruby
api = ApiConnector.new(description: "My cool description", title: "My title")
```

### Overriding default values

What happens if we want to override a default value? We simply update our instantiation call like this:

```ruby
api = ApiConnector.new(title: "My title", description: "My cool description", url: "https://next.xyz")
```

This update will override our default value of `https://next.tech`, and calling `api.testing_initializer` will now print `https://next.xyz` as the URL.

## Inheritance

Now, we are going to learn about an important object-oriented principle called **inheritance**. Before going into how it is executed in Ruby, let’s see why it’s important for building applications.

To start with, inheritance means your classes can have a hierarchy. It is best used when different classes have some shared responsibilities, since it would be a poor practice to duplicate code in each class for identical or even similar behavior.

Take our `ApiConnector` class. Let's say we have different API classes for various platforms, but each class shares a number of common data or processes. Instead of duplicating code in each of the API connector classes, we can have one **parent class** with the shared data and methods. From there, we can create **child classes** from this parent class. With the way that inheritance works, each of the child classes will have access to the components provided from the parent class.

For example, say we have three APIs: `SmsConnector`, `PhoneConnector`, and `MailerConnector`. If we wrote code individually for each of these classes, it would look like this:

```ruby
class SmsConnector
  def initialize(title:, description:, url: "https://next.tech")
    @title = title
    @description = description
    @url = url
  end

  def send_sms
    p "Sending SMS message with the title '#{@title}' and description '#{@description}'"
  end
end

class MailerConnector
  def initialize(title:, description:, url: "https://next.tech")
    @title = title
    @description = description
    @url = url
  end

  def send_mail
    p "Sending mail message with the title '#{@title}' and description '#{@description}'"
  end
end

class PhoneConnector
  def initialize(title:, description:, url: "https://next.tech")
    @title = title
    @description = description
    @url = url
  end

  def place_call
    p "Sending phone call with the title '#{@title}' and description '#{@description}'"
  end
end
```

As you can see, we are simply repeating the same code across different classes. This is considered a poor programming practice that violates the [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) (Don’t Repeat Yourself) principle of development. Instead, we can make an `ApiConnector` parent class, and each of the other classes can inherit the common functionality from this class:

```ruby
class ApiConnector
  def initialize(title:, description:, url: "https://next.tech")
    @title = title
    @description = description
    @url = url
  end
end

class SmsConnector < ApiConnector
  def send_sms
    p "Sending SMS message with the title '#{@title}' and description '#{@description}'"
  end
end

class MailerConnector < ApiConnector
  def send_mail
    p "Sending mail message with the title '#{@title}' and description '#{@description}'"
  end
end

class PhoneConnector < ApiConnector
  def place_call
    p "Sending phone call with the title '#{@title}' and description '#{@description}'"
  end
end
```

By leveraging inheritance, we were able to cut all of the duplicate code throughout our classes.

The syntax for using inheritance is to define the child class name, followed by the `<` symbol, then the parent class name — i.e. our `SmsConnector`, `MailerConnector`, and `PhoneConnector` classes inherit from the `ApiConnector` class .

Each of these child classes now has access to the full set of elements provided in the parent `ApiConnector` class. For example, if we create a new instance of `SmsConnector` with the following parameters, we can call the `send_sms`method:

```ruby
sms = SmsConnector.new(title: "Hi there!", description: "I'm an SMS message")
sms.send_sms
```

```
[Out:]
Sending SMS message with the title 'Hi there!' and description 'I'm an SMS message'.
```

A rule of thumb in OOP is to ensure that a class performs a single responsibility. For example, the `ApiConnector`class should not send SMS messages, make phone calls, or send emails since that would be three core responsibilities.

## Private and Public Methods

Before we dive into private and public methods, let’s first go back to our original `ApiConnector` class and create a `SmsConnector` class that inherits from `ApiConnector`. In this class, we will create a method called `send_sms` that will run a script that contacts an API:

```ruby
class ApiConnector
  def initialize(title:, url: 'https://next.tech')
    @title = title
    @url = url
  end
end

class SmsConnector < ApiConnector
  def send_sms
    `curl -X POST \
    -d "notification[title]=#{@title}" \
    -d "notification[url]=#{@url}" \
    "http://edutechional-smsy.herokuapp.com/notifications"`
  end
end
```

<div class=caption>This SMS API was created by J. Hudgens (2017).</div>

This method will send a `title` and `url` to an API, which will in turn send an SMS message. Now we can instantiate the `SmsConnector` class and call the `send_sms` message:

```ruby
sms = SmsConnector.new(
  title: "Hey there!",
  url: "https://next.tech/xyz/introduction-to-ruby"
  )
sms.send_sms
```

Running this code will contact the SMS API and send the message. You can go to the bottom of [this page](http://edutechional-smsy.herokuapp.com/notifications) to see your message!

Now, using this example, let’s discuss the types of methods provided by classes.

The `send_sms` method is a **public method**. This means that anyone working on our class can communicate with this method. This may not seem like a big deal if you are working on an application that no one else is working on. However, if you build an API or code library that is open sourced for others to use, it's vital that your public methods represent elements of functionality that you actually want other developers to use.

Public methods should rarely, if ever, be altered. This is because other developers may be relying on your public methods to be consistent, and a change to a public method may break components of their programs.

So, if you can’t change public methods, how can you work on a production application? That’s where **private methods** come in. A private method is a method that is only accessed by the class that it is contained in. It should never be called by outside services. This means that you can alter their behavior, assuming that these changes don’t have a domino effect and alter the public methods that they may be called from.

Usually private methods are placed at the end of the file after all the public methods. To designate private methods, we use the `private` word above the list of methods. Let’s add a private method to our `ApiConnector` class:

```ruby
class ApiConnector
  def initialize(title:, url:)
    @title = title
    @url = url
    secret_method
  end

 private

   def secret_method
     p "A secret message from the parent class"
   end
end

api = ApiConnector.new(title: "My Title", url: "https://next.tech")
```

Notice how we're calling this method from the inside of the `initialize` method of the `ApiConnector` class? If we run this code, it will give the following output:

```
[Out:]
A secret message from the parent class
```

Now child classes have access to methods in the parent class, right? Well, not always. Let’s remove the `secret_method` method from the `initialize` method in `ApiConnector` and try to call it from our `SmsConnector` child class, as shown here:

```ruby
class ApiConnector
  def initialize(title:, url:)
    @title = title
    @url = url
  end

 private

   def secret_method
     p "A secret message from the parent class"
   end
end

class SmsConnector < ApiConnector
  def send_sms
    `curl -X POST \
    -d "notification[title]=#{@title}" \
    -d "notification[url]=#{@url}" \
    "http://edutechional-smsy.herokuapp.com/notifications"`
  end
end

sms = SmsConnector.new(
  title: "Hey there!",
  url: "https://next.tech/xyz/introduction-to-ruby"
  )
sms.secret_method
```

```
[Out:]
Traceback (most recent call last):
main.rb:29:in `<main>': private method `secret_method' called for #SmsConnector:0x000056188cfe19b0> (NoMethodError)
```

This is because the `SmsConnector` class only has access to the public methods from the parent class. The private methods are, by their nature, private. This means that they can only be accessed by the class that they are defined in.

So a good rule of thumb is to create private methods when they should not be used outside the class and public methods when they have to be available throughout the application or used by outside services.

------

I hope you enjoyed this quick tutorial on the fundamental concepts of object-oriented programming in Ruby! We covered creating classes, attribute accessors, instantiation, initialization, inheritance, and private and public methods.

Ruby is a powerful object-oriented language used by popular applications, including our own here at Next Tech. With this foundational knowledge of OOP, you’re well on your way to developing your own Ruby apps!

------

*If you’re interested in learning more about programming with Ruby, check out our Introduction to Ruby course [here](https://c.next.tech/2EjW6vY)! In this course we cover core programming skills, such as variables, strings, loops, and conditionals, more advanced OOP topics, and error handling.*