Skip to content

Ruby Classes and Objects

Ruby is a pure Object-Oriented Programming language where everything is an object. This chapter will provide a detailed introduction to classes and objects in Ruby, and how to use them to build programs.

🎯 Object-Oriented Basics

What are Classes and Objects?

  • Class: Template or blueprint for objects, defining attributes and behaviors
  • Object: Instance of a class, with specific attribute values and executable behaviors
ruby
# Everything is an object
puts 42.class        # Integer
puts "Hello".class   # String
puts true.class      # TrueClass
puts nil.class       # NilClass

# View methods of an object
number = 42
puts number.methods  # Display all available methods

🏗️ Class Definition

Basic Class Definition

ruby
# Define a simple class
class Person
  # Class body
end

# Create objects (instantiation)
person1 = Person.new
person2 = Person.new

puts person1.class  # Person
puts person2.class  # Person
puts person1.object_id  # Object ID
puts person2.object_id  # Different object ID

Constructor initialize

ruby
class Person
  # Constructor (initialization method)
  def initialize(name, age)
    @name = name  # Instance variable
    @age = age
  end
  
  # Instance method
  def introduce
    puts "I am #{@name}, #{@age} years old"
  end
end

# Create object and call method
person = Person.new("Alice", 25)
person.introduce  # Output: I am Alice, 25 years old

Accessor Methods

ruby
class Person
  def initialize(name, age)
    @name = name
    @age = age
  end
  
  # getter methods
  def name
    @name
  end
  
  def age
    @age
  end
  
  # setter methods
  def name=(name)
    @name = name
  end
  
  def age=(age)
    @age = age
  end
  
  def introduce
    puts "I am #{@name}, #{@age} years old"
  end
end

# Using accessor methods
person = Person.new("Bob", 30)
puts person.name    # Bob
person.age = 31
puts person.age     # 31

Using attr_accessor to Simplify Accessors

ruby
class Person
  # Automatically generate getter and setter methods
  attr_accessor :name, :age
  # Equivalent to:
  # attr_reader :name, :age  # Generate only getter
  # attr_writer :name, :age  # Generate only setter
  
  def initialize(name, age)
    @name = name
    @age = age
  end
  
  def introduce
    puts "I am #{@name}, #{@age} years old"
  end
end

# Using simplified accessors
person = Person.new("Charlie", 28)
puts person.name      # Charlie
person.name = "David"
person.age = 29
person.introduce      # I am David, 29 years old

📦 Variable Types

Instance Variables

ruby
class Counter
  def initialize
    @count = 0  # Instance variable, starts with @
  end
  
  def increment
    @count += 1
  end
  
  def count
    @count
  end
end

counter1 = Counter.new
counter2 = Counter.new

counter1.increment
counter1.increment
puts counter1.count  # 2
puts counter2.count  # 0 (different instance)

Class Variables

ruby
class Counter
  @@total_instances = 0  # Class variable, starts with @@
  
  def initialize
    @count = 0
    @@total_instances += 1
  end
  
  def increment
    @count += 1
  end
  
  def count
    @count
  end
  
  # Class method to access class variable
  def self.total_instances
    @@total_instances
  end
end

counter1 = Counter.new
counter2 = Counter.new
counter3 = Counter.new

puts Counter.total_instances  # 3

Global Variables

ruby
# Global variable, starts with $ (not recommended for heavy use)
$global_counter = 0

class GlobalCounter
  def increment
    $global_counter += 1
  end
  
  def self.global_count
    $global_counter
  end
end

counter = GlobalCounter.new
counter.increment
counter.increment
puts GlobalCounter.global_count  # 2

Local Variables and Constants

ruby
class MathConstants
  PI = 3.14159  # Constant, uppercase naming
  E = 2.71828
  
  def self.calculate_circle_area(radius)
    local_variable = radius * radius  # Local variable
    PI * local_variable
  end
end

puts MathConstants::PI  # 3.14159
puts MathConstants.calculate_circle_area(5)  # 78.53975

🎯 Method Types

Instance Methods

ruby
class Calculator
  def add(a, b)
    a + b
  end
  
  def subtract(a, b)
    a - b
  end
end

calc = Calculator.new
puts calc.add(10, 5)      # 15
puts calc.subtract(10, 5) # 5

Class Methods

ruby
class MathUtils
  # Method 1: Using self
  def self.square(number)
    number * number
  end
  
  # Method 2: Using class name
  def MathUtils.cube(number)
    number * number * number
  end
  
  # Method 3: Define inside class
  class << self
    def power(base, exponent)
      base ** exponent
    end
  end
end

puts MathUtils.square(4)    # 16
puts MathUtils.cube(3)      # 27
puts MathUtils.power(2, 3)  # 8

Private Methods

ruby
class BankAccount
  def initialize(balance)
    @balance = balance
  end
  
  def deposit(amount)
    if valid_amount?(amount)
      @balance += amount
      puts "Deposit successful, current balance: #{@balance}"
    else
      puts "Invalid amount"
    end
  end
  
  def withdraw(amount)
    if valid_amount?(amount) && sufficient_funds?(amount)
      @balance -= amount
      puts "Withdrawal successful, current balance: #{@balance}"
    else
      puts "Withdrawal failed"
    end
  end
  
  def balance
    @balance
  end
  
  private  # Private methods
  
  def valid_amount?(amount)
    amount > 0
  end
  
  def sufficient_funds?(amount)
    @balance >= amount
  end
end

account = BankAccount.new(1000)
account.deposit(500)    # Deposit successful, current balance: 1500
account.withdraw(200)   # Withdrawal successful, current balance: 1300
# account.valid_amount?(100)  # Error: private method

Protected Methods

ruby
class Animal
  def initialize(name)
    @name = name
  end
  
  protected  # Protected methods
  
  def speak
    "#{@name} makes a sound"
  end
end

class Dog < Animal
  def initialize(name)
    super(name)
  end
  
  def bark
    speak + " Woof!"  # Can call parent's protected method
  end
end

dog = Dog.new("Buddy")
puts dog.bark  # Buddy makes a sound Woof!
# dog.speak    # Error: protected method

🧬 Inheritance

Basic Inheritance

ruby
# Parent class
class Vehicle
  def initialize(brand, model)
    @brand = brand
    @model = model
  end
  
  def start_engine
    puts "#{@brand} #{@model} engine started"
  end
  
  def stop_engine
    puts "#{@brand} #{@model} engine stopped"
  end
  
  def info
    "#{@brand} #{@model}"
  end
end

# Subclass
class Car < Vehicle
  def initialize(brand, model, doors)
    super(brand, model)  # Call parent class constructor
    @doors = doors
  end
  
  def open_trunk
    puts "Open trunk"
  end
  
  # Override parent method
  def info
    super + " (#{@doors} doors)"
  end
end

# Using inheritance
car = Car.new("Toyota", "Corolla", 4)
car.start_engine     # Toyota Corolla engine started
car.open_trunk       # Open trunk
puts car.info        # Toyota Corolla (4 doors)

Method Lookup Chain

ruby
class A
  def method1
    "A's method1"
  end
end

class B < A
  def method1
    "B's method1"
  end
  
  def method2
    "B's method2"
  end
end

class C < B
  def method1
    "C's method1"
  end
end

obj = C.new
puts obj.method1  # C's method1
puts obj.method2  # B's method2 (inherited from B)

🔄 Modules and Mixins

Module Definition

ruby
# Define module
module Flyable
  def fly
    "I'm flying!"
  end
  
  def land
    "I landed!"
  end
end

module Swimmable
  def swim
    "I'm swimming!"
  end
end

# Using modules
class Bird
  include Flyable  # Include module
end

class Fish
  include Swimmable
end

class Duck
  include Flyable
  include Swimmable
end

bird = Bird.new
puts bird.fly    # I'm flying!

duck = Duck.new
puts duck.fly    # I'm flying!
puts duck.swim   # I'm swimming!

Modules as Namespaces

ruby
module MathUtils
  PI = 3.14159
  
  class Calculator
    def self.add(a, b)
      a + b
    end
  end
  
  def self.square(number)
    number * number
  end
end

puts MathUtils::PI              # 3.14159
puts MathUtils::Calculator.add(2, 3)  # 5
puts MathUtils.square(4)        # 16

🎭 Polymorphism

Method Overriding and Polymorphism

ruby
class Animal
  def speak
    "Animal makes a sound"
  end
end

class Dog < Animal
  def speak
    "Woof!"
  end
end

class Cat < Animal
  def speak
    "Meow!"
  end
end

class Bird < Animal
  def speak
    "Chirp chirp!"
  end
end

# Polymorphism example
animals = [Dog.new, Cat.new, Bird.new]

animals.each do |animal|
  puts animal.speak  # Calls appropriate method based on object type
end
# Output:
# Woof!
# Meow!
# Chirp chirp!

🧱 Object Lifecycle

Initialization and Cleanup

ruby
class Resource
  def initialize(name)
    @name = name
    puts "Creating resource: #{@name}"
  end
  
  def use
    puts "Using resource: #{@name}"
  end
  
  # Called before garbage collection
  def finalize
    puts "Cleaning up resource: #{@name}"
  end
  
  # Custom destructor method
  def dispose
    puts "Manually cleaning up resource: #{@name}"
  end
end

# Object lifecycle
resource = Resource.new("Database connection")
resource.use
resource.dispose  # Manual cleanup

# resource variable will be garbage collected after going out of scope

🎯 Advanced Features

Singleton Class

ruby
class MyClass
  def instance_method
    "Instance method"
  end
  
  class << self
    def class_method
      "Class method"
    end
  end
end

# Singleton methods
obj = MyClass.new

class << obj
  def singleton_method
    "Singleton method"
  end
end

puts obj.instance_method    # Instance method
puts MyClass.class_method   # Class method
puts obj.singleton_method   # Singleton method

# Check singleton class
puts obj.singleton_class
puts MyClass.singleton_class

Method Missing Handling

ruby
class FlexibleObject
  def initialize
    @data = {}
  end
  
  def method_missing(method_name, *args, &block)
    if method_name.to_s.end_with?('=')
      # Setter method
      key = method_name.to_s.chomp('=').to_sym
      @data[key] = args.first
    else
      # Getter method
      @data[method_name]
    end
  end
  
  def respond_to_missing?(method_name, include_private = false)
    true
  end
end

obj = FlexibleObject.new
obj.name = "Alice"
obj.age = 25
puts obj.name  # Alice
puts obj.age   # 25

🧪 Practice Examples

Complete Bank Account System

ruby
class BankAccount
  attr_reader :account_number, :balance
  attr_accessor :owner
  
  @@total_accounts = 0
  
  def initialize(owner, initial_balance = 0)
    @owner = owner
    @balance = initial_balance
    @account_number = generate_account_number
    @@total_accounts += 1
  end
  
  def deposit(amount)
    if amount > 0
      @balance += amount
      log_transaction("Deposit", amount)
      true
    else
      puts "Deposit amount must be greater than 0"
      false
    end
  end
  
  def withdraw(amount)
    if amount > 0 && amount <= @balance
      @balance -= amount
      log_transaction("Withdrawal", amount)
      true
    elsif amount > @balance
      puts "Insufficient balance"
      false
    else
      puts "Withdrawal amount must be greater than 0"
      false
    end
  end
  
  def transfer_to(other_account, amount)
    if withdraw(amount)
      other_account.deposit(amount)
      puts "Transfer successful: #{amount} to #{other_account.owner}"
    end
  end
  
  def self.total_accounts
    @@total_accounts
  end
  
  def info
    "Account: #{@account_number}, Owner: #{@owner}, Balance: #{@balance}"
  end
  
  private
  
  def generate_account_number
    "ACC#{rand(100000..999999)}"
  end
  
  def log_transaction(type, amount)
    timestamp = Time.now.strftime("%Y-%m-%d %H:%M:%S")
    puts "[#{timestamp}] #{type}: #{amount}, Balance: #{@balance}"
  end
end

# Using bank account system
account1 = BankAccount.new("Alice", 1000)
account2 = BankAccount.new("Bob", 500)

puts account1.info
puts account2.info

account1.deposit(200)
account1.withdraw(150)
account1.transfer_to(account2, 300)

puts "Total accounts: #{BankAccount.total_accounts}"

🎯 Object-Oriented Best Practices

1. Single Responsibility Principle

ruby
# Good design: Each class has one responsibility
class User
  attr_accessor :name, :email
  
  def initialize(name, email)
    @name = name
    @email = email
  end
end

class UserValidator
  def self.valid_email?(email)
    email.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
  end
end

class UserPersistence
  def self.save(user)
    # Save user to database
  end
end

2. Encapsulation

ruby
class Temperature
  def initialize(celsius)
    self.celsius = celsius
  end
  
  def celsius
    @celsius
  end
  
  def celsius=(value)
    raise ArgumentError, "Temperature cannot be below absolute zero (-273.15°C)" if value < -273.15
    @celsius = value
  end
  
  def fahrenheit
    @celsius * 9.0 / 5.0 + 32
  end
  
  def fahrenheit=(value)
    self.celsius = (value - 32) * 5.0 / 9.0
  end
end

temp = Temperature.new(25)
puts temp.celsius     # 25
puts temp.fahrenheit  # 77.0
temp.fahrenheit = 86
puts temp.celsius     # 30.0

3. Inheritance Hierarchy Design

ruby
# Abstract base class
class Shape
  def area
    raise NotImplementedError, "Subclass must implement area method"
  end
  
  def perimeter
    raise NotImplementedError, "Subclass must implement perimeter method"
  end
end

class Rectangle < Shape
  def initialize(width, height)
    @width = width
    @height = height
  end
  
  def area
    @width * @height
  end
  
  def perimeter
    2 * (@width + @height)
  end
end

class Circle < Shape
  def initialize(radius)
    @radius = radius
  end
  
  def area
    Math::PI * @radius ** 2
  end
  
  def perimeter
    2 * Math::PI * @radius
  end
end

# Using polymorphism
shapes = [
  Rectangle.new(5, 3),
  Circle.new(4)
]

shapes.each do |shape|
  puts "Area: #{shape.area.round(2)}, Perimeter: #{shape.perimeter.round(2)}"
end

📚 Next Steps

After mastering Ruby classes and objects, continue learning:

Continue your Ruby learning journey!

Content is for learning and research only.