Object-Oriented Programming (OOP) in Ruby
Object-Oriented Programming (OOP) is a programming paradigm that revolves around the concept of objects. If you are familiar with JavaScript, the word ‘object’ might bring to mind the object data type (i.e. objectName = {key: value}). However, in Ruby and other object-oriented programming languages, objects can be defined as instances of classes that are used to represent real-world entities, such as people, animals, cars, etc. In OOP, these objects then encapsulate both data and the methods that manipulate that data. Ruby has a rich set of object-oriented features, including abstraction, inheritance, encapsulation, and polymorphism, which make it an ideal language for developing OOP-based applications. While several languages operate under the OOP paradigm, Ruby is considered to be one of the most purely object-oriented programming languages around. On his inspiration for developing Ruby in the mid-90s, creator Yukihiro Matsumoto wrote:
“I was talking with my colleague about the possibility of an object-oriented scripting language. I knew Perl (Perl4, not Perl5), but I didn’t like it really, because it had the smell of a toy language (it still has). The object-oriented language seemed very promising. I knew Python then. But I didn’t like it, because I didn’t think it was a true object-oriented language — OO features appeared to be add-on to the language. As a language maniac and OO fan for 15 years, I really wanted a genuine object-oriented, easy-to-use scripting language. I looked for but couldn’t find one. So I decided to make it.”
In Ruby, everything is considered to be an object. Even basic data types, such as integers and strings, have their own set of methods that can be enacted on them.
-14.abs
=> 14
"Ruby is an awesome OOP language".length
=> 31We can even trace back the ancestry of these data types using the built-in .ancestors method to prove that they are objects.
String.ancestors
=> [String, Comparable, Object, Kernel, BasicObject]Classes in Ruby are considered to be the blueprint for creating objects in Ruby, which also define the behaviors and attributes of the objects they create. Therefore, classes can be thought of as ‘object factories.’ For example, the class Shoes can create individual objects (a.k.a. instances of the Shoes class) such as Jordans, Converse, or Margiela Tabis.
class Shoes
attr_accessor :brand, :name
def initialize(brand, name)
@brand = brand
@name = name
end
controversial_shoe = Shoes.new('Maison Margiela', 'Tabi')
end
=> #<Shoes:0x00007fc96b82f320 @brand="Maison Margiela", @name="Tabi"> Abstraction
Abstraction is the one of the four pillars of object-oriented programming, which refers to the idea of exposing or hiding specific details of functionality as defined in classes or modules. The goal of abstraction in OOP is to reduce complexity and increase code reusability by allowing users to better understand what an object does, how it behaves, and how it should be interacted with. An example of this in Ruby is the use of methods and access-controls like private, protected, and public. Consider the example below:
# Define a class with implementation details hidden
class Animal
attr_reader :name, :age
def initialize(name, age)
@name = name
@age = age
end
def sound
raise NotImplementedError, "Subclasses must implement this method"
end
private
def speak
puts "#{name} says #{sound}"
end
end
# Create a subclass that implements the abstract method
class Dog < Animal
def sound
"Woof!"
end
end
# Create an instance of the subclass and call the abstract method
dog = Dog.new("Fido", 5)
dog.speak # Raises an error: private method `speak' called for #<Dog:0x00007fbbf4110cc8> (NoMethodError)In this example, the #speak method has been made private, making it inaccessible from outside the Animal class. This means that trying to call the speak method on an instance of the Dog class will result in a NoMethodError. This demonstrates how the private access control can be used to hide the implementation details of a class, enforcing abstraction.
Inheritance
Inheritance is the second pillar of OOP. Classes can also inherit behaviors from other classes (one of the fundamental principles of OOP), allowing you to create new classes that are based on existing ones. This makes it possible to reuse code and create more complex, scalable class structures.
Get Caleigh Steill’s stories in your inbox
Join Medium for free to get updates from this writer.
Consider this example:
class Human
def walk
"Walking"
end
def breathe
"Breathing"
end
end
class Man < Human
end
class Woman < Human
end
adam = Man.new
adam.breathe # => "Breathing"
eve = Woman.new
eve.breathe # => "Breathing"The Man and Woman classes inherit the behaviors of the parent Human class, which supersedes them, giving them the Human abilities of breathing (#breathe) and walking (#walk).
Polymorphism
Like inheritance, polymorphism is another pillar of object-oriented programming. In Ruby, polymorphism can be considered a consequence of inheritance, as it is really the only practical application of polymorphism in the language. With polymorphism, we are able to run the same method with different objects, thus, we can obtain different results using the same function by passing different input objects.
# Ruby program of Polymorphism using inheritance
class Vehicle
def tire_type
puts "Heavy Car"
end
end
# Using inheritance
class Car < Vehicle
def tire_type
puts "Small Car"
end
end
# Using inheritance
class Truck < Vehicle
def tire_type
puts "Big Car"
end
end
# Creating object
vehicle = Vehicle.new
vehicle.tire_type
# Creating different object calling same function
vehicle = Car.new
vehicle.tire_type
# Creating different object calling same function
vehicle = Truck.new
vehicle.tire_type
# Outputs:
Heavy Car
Small Car
Big CarEncapsulation
Encapsulation is another key aspect of OOP, which refers to the refers to the bundling of data, along with the methods that operate on that data, into a single unit. Classes in Ruby are an example of one of the ways in which we can group together data and methods. Another example of the enforcement of encapsulation in Ruby is the ability to restrict access to methods and variables using the private keyword. This makes it possible to control how objects interact with each other and keeps both the data and the methods that operate on that data safe from outside interference and misuse.
# Define a class that encapsulates data and behavior
class BankAccount
attr_reader :balance
def initialize(balance)
@balance = balance
end
def deposit(amount)
@balance += amount
end
def withdraw(amount)
@balance -= amount if @balance >= amount
end
end
# Create an instance of the class
account = BankAccount.new(1000)
# Interact with the object using its methods
account.deposit(100)
puts "Balance: $#{account.balance}" # Output: Balance: $1100
account.withdraw(500)
puts "Balance: $#{account.balance}" # Output: Balance: $600In this example, the BankAccount class encapsulates both data (the balance instance variable) and behavior (the #deposit and #withdraw methods). The balance instance variable is made accessible through the balance getter method (attr_reader :balance), but is not directly accessible from outside the class. This means that the internal state of the object can only be modified through its methods, enforcing the principle of encapsulation and promoting data protection.
To recap:
“The abstraction is simplifying complex reality by modeling classes appropriate to the problem. The polymorphism is the process of using an operator or method in different ways for different data input. The encapsulation hides the implementation details of a class from other objects. The inheritance is a way to form new classes using classes that have already been defined.”
Resources:






