Object-Oriented Programming (OOP) in Ruby - Beginner Crash Course
Tags: ruby, beginners, tutorial
Published: 2023-05-16 09:39:13

Ruby is a powerful and dynamic programming language known for its simplicity and readability. One of the key features that makes Ruby a popular choice among developers is its support for Object-Oriented Programming (OOP). In this beginner crash course, we will explore the fundamental concepts of OOP in Ruby, including classes, objects, inheritance, and more. So let's dive in!


## Classes and Objects

In Ruby, everything is an object, and objects are instances of classes. Classes act as blueprints or templates that define the properties (attributes) and behaviors (methods) of objects. Let's start by creating a simple class and an object:

```ruby
class Car
  def initialize(make, model)
    @make = make
    @model = model
  end

  def start_engine
    puts "The #{@make} #{@model}'s engine is running!"
  end
end

my_car = Car.new("Toyota", "Camry")
my_car.start_engine
```

In the above code, we defined a class named Car with an initialize method that sets the make and model instance variables. The start_engine method is used to start the car's engine. We then create an object my_car of the Car class using the new method and invoke the start_engine method on it.

## Attributes and Accessors

In Ruby, instance variables prefixed with @ are used to store object-specific data. To access and modify these instance variables, we can define getter and setter methods using attribute accessors. Let's enhance our Car class to include additional attributes:

```ruby
class Car
  attr_accessor :make, :model, :color

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

  def start_engine
    puts "The #{@make} #{@model}'s engine is running!"
  end
end

my_car = Car.new("Toyota", "Camry", "blue")
puts "My car is a #{my_car.color} #{@make} #{@model}."
```

In this updated code, we added the attr_accessor line to define getter and setter methods for the make, model, and color attributes. We also passed the color parameter to the initialize method, and we can now access the car's color using the color accessor.

## Inheritance

Inheritance allows us to create a hierarchy of classes, where a child class (subclass) inherits the properties and behaviors of a parent class (superclass). Let's demonstrate inheritance using a Vehicle superclass and a Car subclass:

```ruby
class Vehicle
  attr_accessor :make, :model

  def initialize(make, model)
    @make = make
    @model = model
  end

  def start_engine
    puts "The #{@make} #{@model}'s engine is running!"
  end
end

class Car < Vehicle
  attr_accessor :color

  def initialize(make, model, color)
    super(make, model)
    @color = color
  end
end

my_car = Car.new("Toyota", "Camry", "blue")
my_car.start_engine
```

In this example, the Vehicle class serves as the superclass, and the Car class inherits from it using the < symbol. The Car class includes the color attribute and overrides the initialize method using the super keyword to invoke the superclass's initialize method.


## Testing

Now, let's write some tests to validate the behavior of our classes using Ruby's built-in testing framework, MiniTest:

```ruby
require 'minitest/autorun'

class CarTest < Minitest::Test
  def setup
    @car = Car.new("Toyota", "Camry", "blue")
  end

  def test_start_engine
    assert_output("The Toyota Camry's engine is running!\n") { @car.start_engine }
  end

  def test_color_accessor
    assert_equal("blue", @car.color)
  end
end
```

In this testing code, we create a CarTest class that inherits from Minitest::Test. The setup method is called before each test, where we create an instance of the Car class. We then define test methods that use assertions to verify the expected output and behavior of our code.

## Advanced Tip: Utilizing Modules for Code Reusability and Composition

In addition to classes and inheritance, Ruby provides another powerful feature called modules. Modules allow you to encapsulate reusable code and mix it into classes using the include keyword. This technique promotes code reusability and enables composition, allowing you to combine functionality from multiple modules.

```ruby
module Greetings
  def greet
    puts "Hello!"
  end
end

module Farewells
  def say_goodbye
    puts "Goodbye!"
  end
end

class Person
  include Greetings
  include Farewells
end

person = Person.new
person.greet        # Output: Hello!
person.say_goodbye  # Output: Goodbye!
```

In the above code, we defined two modules, Greetings and Farewells, each containing a single method. Then, we created a Person class and included both modules using the include keyword. As a result, instances of the Person class now have access to the methods defined in the modules.

Using modules in this way allows you to organize and reuse common functionality across multiple classes without resorting to repetitive code. It promotes cleaner code architecture and better separation of concerns, making your codebase more maintainable and scalable.

Another powerful aspect of modules is that they support namespacing, allowing you to avoid naming conflicts between methods with the same name. By defining methods within different modules, you can keep related functionality grouped together and avoid clashes.

```ruby
module MathOperations
  def self.square(number)
    number * number
  end
end

module FileOperations
  def self.square(number)
    number ** 2
  end
end

MathOperations.square(5)  # Output: 25
FileOperations.square(5)  # Output: 25
```

In this example, we have two modules, MathOperations and FileOperations, each defining a square method. Since the methods are defined within their respective modules and called using the module name, there is no conflict between the two methods.

By leveraging modules, you can achieve code reuse, better organization, and enhanced code composition in your Ruby applications. It is a powerful feature that can greatly improve the structure and maintainability of your codebase.

Remember to experiment with modules and explore other advanced concepts such as module mixins, method overrides, and module inheritance to further expand your understanding and utilization of this feature.

## Conclusion

Object-Oriented Programming is a fundamental concept in Ruby and provides a powerful way to organize and structure code. In this crash course, we covered the basics of classes, objects, attributes, accessors, inheritance, and testing. By mastering these concepts, you'll be well on your way to building robust and scalable Ruby applications. Happy coding!

Remember to practice writing code and experiment with different scenarios to solidify your understanding of Object-Oriented Programming in Ruby.