Understanding Ruby’s Object Model Through Practical Examples

Understanding Ruby’s Object Model Through Practical Examples

Ruby is built around objects. Numbers, strings, arrays, classes, and even methods are connected to an object-oriented structure. This idea may seem abstract at first, but it becomes much clearer when you examine how data and behavior are grouped together.

An object can represent a real or conceptual item inside a program. For example, a book may have a title, an author, and a page count. It may also perform actions such as showing its details or changing its reading status. Ruby classes allow developers to describe these properties and actions in one organized place.

Consider this class:

class Book
  def initialize(title, author, pages)
    @title = title
    @author = author
    @pages = pages
  end

  def summary
    "#{@title} by #{@author}, #{@pages} pages"
  end
end

The Book class acts as a description for objects created from it. The initialize method runs when a new object is created. The values passed into this method are stored in instance variables.

book = Book.new("Quiet Architecture", "Nora Bell", 280)
puts book.summary

The variable book now refers to one object created from the Book class. That object stores its own title, author, and page count. Another object created from the same class can hold different values.

second_book = Book.new("Data Paths", "Milo Carter", 190)
puts second_book.summary

Both objects share the same class structure, but each keeps its own internal state.

Instance Variables and Object State

Instance variables begin with the @ symbol. They belong to an individual object and remain available between method calls.

class ReadingList
  def initialize
    @books = []
  end

  def add(book)
    @books << book
  end

  def count
    @books.length
  end
end

Each ReadingList object has its own array. Adding a book to one list does not change another list.

work_list = ReadingList.new
weekend_list = ReadingList.new

work_list.add("Ruby Notes")

puts work_list.count
puts weekend_list.count

This separation is important because it allows the program to manage several independent objects without mixing their data.

Reading and Updating Attributes

Sometimes outside code needs to read or update values stored inside an object. Ruby provides attribute helpers for this purpose.

class Member
  attr_reader :name
  attr_accessor :status

  def initialize(name, status)
    @name = name
    @status = status
  end
end

attr_reader creates a method that reads a value. attr_accessor creates methods for both reading and updating.

member = Member.new("Elian Brooks", "active")

puts member.name
puts member.status

member.status = "paused"
puts member.status

These helpers reduce repeated code while keeping the class readable.

Methods as Object Behavior

Methods describe what an object can do. A method may read object data, update it, calculate a value, or prepare output.

class Invoice
  def initialize(items)
    @items = items
  end

  def total
    @items.sum
  end

  def item_count
    @items.length
  end
end

The Invoice object stores a collection of values and provides methods that work with those values.

invoice = Invoice.new([25, 40, 15])

puts invoice.total
puts invoice.item_count

This structure keeps the data and the related operations in one place.

Class Methods

Some behavior belongs to the class rather than to a single object. Class methods are created with self.

class Temperature
  def self.celsius_to_fahrenheit(value)
    (value * 9.0 / 5) + 32
  end
end

puts Temperature.celsius_to_fahrenheit(20)

No Temperature object is required because the calculation does not depend on stored object state.

Object Collaboration

Ruby programs often use several objects that work together.

class Cart
  def initialize
    @items = []
  end

  def add(item)
    @items << item
  end

  def total
    @items.sum(&:price)
  end
end

class Item
  attr_reader :name, :price

  def initialize(name, price)
    @name = name
    @price = price
  end
end

The Cart object stores Item objects. It asks each item for its price when calculating the total.

cart = Cart.new
cart.add(Item.new("Notebook", 12))
cart.add(Item.new("Pen Set", 8))

puts cart.total

This example shows collaboration without placing every responsibility inside one class.

Keeping Responsibilities Focused

A class is easier to understand when it has one clear role. A class that stores data, validates input, formats reports, writes files, and sends messages can become difficult to review.

A cleaner approach is to divide these tasks.

class Report
  attr_reader :entries

  def initialize(entries)
    @entries = entries
  end
end

class ReportFormatter
  def initialize(report)
    @report = report
  end

  def as_text
    @report.entries.join("\n")
  end
end

The Report class stores entries. The ReportFormatter class prepares the text. Each class has a distinct purpose.

Practice Exercise

Create a Playlist class with the following features:

  • an array of song titles;
  • a method for adding a song;
  • a method for removing a song;
  • a method that returns the number of songs;
  • a method that returns all songs as a formatted string.

Then create two playlist objects and confirm that each one stores its own data.

Ruby’s object model becomes easier to understand through regular practice. Start with small classes, give each one a clear role, and observe how objects store data and respond to method calls. This approach builds a structured understanding of how larger Ruby programs are organized.

Back to blog