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.