Classes & Objects

Ruby Fundamentals — Classes & Objects
Ruby Fundamentals
Course 1 · Chapter 6 · Classes & Objects

🏛️ Classes & Objects

Every chapter so far has used objects Ruby already provides — strings, arrays, hashes. This chapter shows how to build your own. Ruby's class syntax will feel broadly familiar coming from PHP or Python, but two details are genuinely different: how instance variables are written, and how little ceremony Ruby needs to turn them into public getters and setters.

🏗️ Defining a Class and initialize

class Person def initialize(name, age) @name = name @age = age end end philip = Person.new("Philip", 33)

initialize is Ruby's constructor — it runs automatically whenever .new is called, the same role as PHP's __construct or Python's __init__. Class names are capitalized by convention (CamelCase, not snake_case) — this isn't just style, Ruby actually treats capitalized identifiers as constants, and class names are constants.

📌 Instance Variables — the @ Sigil

This is the first genuinely new syntax in this chapter. Ruby instance variables are written with a leading @, directly on the variable name — no $this-> and no self. required to declare or read one from inside an instance method:

class Person def initialize(name) @name = name end def greet "Hi, I'm #{@name}" # @name is read directly -- no self./this-> prefix needed end end

Instance Variables: PHP vs Python vs Ruby

PHPPythonRuby
Declaring/setting$this->name = $name;self.name = name@name = name
Reading inside a method$this->nameself.name@name
Requires an explicit receiver?Yes — $this->Yes — self.No — @ is the whole syntax
⚠ An uninitialized instance variable is nil, not an error

Referencing @nickname before it's ever been assigned doesn't raise an error the way accessing an undeclared property might elsewhere — it silently evaluates to nil. This is convenient for quick scripts, but it means a typo in an instance variable name (@nmae instead of @name) fails silently instead of raising a clear error, which can be a genuinely confusing bug to track down.

🚪 attr_accessor, attr_reader, attr_writer

By default, instance variables are private to the object — there's no automatic public getter or setter the way some languages provide. Writing them by hand gets repetitive fast:

# The manual way class Person def initialize(name) @name = name end def name # manual getter @name end def name=(value) # manual setter -- note the = is part of the method name @name = value end end

Ruby collapses that entire pattern into one line:

class Person attr_accessor :name, :age # generates BOTH a getter and setter for each def initialize(name, age) @name = name @age = age end end philip = Person.new("Philip", 33) philip.name # => "Philip" -- the generated getter philip.name = "P." # the generated setter

attr_reader — getter only

attr_reader :name generates only the getter, leaving the instance variable read-only from outside the class. Use this for values that shouldn't be changed after construction — an ID, a creation timestamp, anything meant to stay fixed.

attr_writer — setter only

attr_writer :password generates only the setter, with no way to read the value back out through a public method. Rare in practice, but useful for write-only style properties.

Getters/Setters: PHP vs Python vs Ruby

PHPPythonRuby
ApproachWrite getName()/setName() by hand, or PHP 8.1+ readonly properties@property / @name.setter decoratorsattr_accessor / attr_reader / attr_writer
Lines needed for a full getter+setter~6-10~6-81 (attr_accessor :name)

📝 Customizing to_s

Every Ruby object has a default to_s that produces an unhelpful string like #<Person:0x00007f8b1a0b8c a8>. Override it to control how an object prints — the equivalent of PHP's __toString or Python's __str__:

class Person attr_accessor :name, :age def initialize(name, age) @name = name @age = age end def to_s "#{@name} (#{@age})" end end puts Person.new("Philip", 33) # => Philip (33) -- puts calls to_s automatically

⚡ Class Methods — def self.method_name

A method defined with self. belongs to the class itself, not to individual instances — the equivalent of PHP's static methods or Python's @staticmethod/@classmethod:

class Person def self.species "Homo sapiens" end end Person.species # => "Homo sapiens" -- called on the class, no instance needed

💻 Coding Challenges

Challenge 1: A Book Class

Write a Book class with attr_accessor for :title, :author, and :pages, plus an initialize that sets all three. Create two instances and print each one's title using the generated getter.

Goal: Get comfortable with the initialize + attr_accessor pattern that appears in nearly every Ruby class.

→ Solution

Challenge 2: Custom to_s

Add a to_s method to your Book class from Challenge 1 that returns a formatted string like "'Title' by Author (pages pp.)". Use puts on an instance directly to confirm it's picked up automatically.

Goal: Practice overriding a default Ruby method to control an object's string representation.

→ Solution

Challenge 3: A Class Method Counter

Modify the Book class to track how many books have been created: add a class variable (research @@count briefly, or use a class-level instance variable with self.count) that increments inside initialize, and a class method self.total_books that returns the current count.

Goal: See class-level state and a class method working together, distinct from any single instance.

→ Solution

💎 Default to attr_accessor, Narrow When You Have a Reason

It's tempting to always reach for attr_accessor since it's the shortest to type, but the moment a value shouldn't be freely rewritten from outside the class — an ID, a total that should only change through a specific method — switch to attr_reader and add an explicit method for any controlled mutation. Default to the more restrictive option and only open things up when you actually have a reason to.

🎯 What's Next

Next chapter: Modules & Mixinsinclude vs extend, and how Ruby gets the benefits of multiple inheritance without actually allowing a class to have more than one superclass.