ZetCode

Ruby Object-Oriented Programming

last modified October 18, 2026

Ruby is a fully object-oriented language: every value is an object, and every object is an instance of a class. This guide ties together the core object-oriented concepts in Ruby, including classes, inheritance, modules, encapsulation, and polymorphism. Each example is a small self-contained program with its output.

Classes, Instances, and Class Members

A class is a blueprint for objects, and new creates instances of it. This example shows instance variables, class variables (@@count), a class-level method, and a constant. The attr_accessor call generates getter and setter methods for name and age.

classes_instances.rb
class Dog
  attr_accessor :name, :age
  @@count = 0
  SPECIES = "Canis lupus familiaris"

  def initialize(name, age)
    @name = name
    @age = age
    @@count += 1
  end

  def self.count
    @@count
  end

  def bark
    "#{@name} says Woof!"
  end
end

rex = Dog.new("Rex", 3)
fido = Dog.new("Fido", 5)

puts rex.bark
puts fido.name
puts "Dogs created: #{Dog.count}"
puts Dog::SPECIES
puts rex.is_a?(Dog)

The initialize method runs automatically when new is called and sets up the instance variables. The class variable @@count is shared by all instances, and self.count is a class method called on Dog itself. Constants like SPECIES are accessed with ::. For the class definition itself see the class keyword page.

$ ./classes_instances.rb
Rex says Woof!
Fido
Dogs created: 2
Canis lupus familiaris
true

Inheritance and super

Subclasses inherit behavior from a superclass with the < operator. The super keyword calls the parent implementation, both with and without arguments. This example also shows method overriding through speak.

inheritance_super.rb
class Animal
  attr_reader :name

  def initialize(name)
    @name = name
  end

  def speak
    "#{@name} makes a sound"
  end
end

class Cat < Animal
  def initialize(name, indoor)
    super(name)
    @indoor = indoor
  end

  def speak
    "#{super} (meow)"
  end

  def indoor?
    @indoor
  end
end

cat = Cat.new("Tom", true)
puts cat.speak
puts cat.indoor?
puts Cat.superclass
puts cat.is_a?(Animal)

Cat inherits from Animal. Its initialize calls super(name) to set the name in the parent, then sets its own @indoor value. The speak method overrides the parent version and reuses it via a bare super, which passes the same arguments. Details are on the super keyword page.

$ ./inheritance_super.rb
Tom makes a sound (meow)
true
Animal
true

Modules as Mixins

Ruby has single inheritance, but a class can mix in behavior from modules with include. The module's methods become instance methods of the including class and appear in its ancestor chain.

modules_mixins.rb
module Greetable
  def greet
    "Hello, I am #{name}"
  end
end

module Walkable
  def walk
    "#{name} is walking"
  end
end

class Person
  include Greetable
  include Walkable
  attr_reader :name

  def initialize(name)
    @name = name
  end
end

alice = Person.new("Alice")
puts alice.greet
puts alice.walk
puts Person.include?(Greetable)
puts Person.ancestors.take(4).inspect

Greetable and Walkable are modules that rely on a name method provided by the class. The ancestors array shows the lookup order: the class first, then the most recently included module, then the earlier one. See the module keyword and include keyword pages for more.

$ ./modules_mixins.rb
Hello, I am Alice
Alice is walking
true
[Person, Walkable, Greetable, Object]

Encapsulation with public, protected, and private

Ruby controls method access with visibility modifiers. Public methods form the interface, protected methods can be called by other instances of the same class, and private methods can only be called on the implicit receiver. The private and protected keywords switch the visibility of all methods defined after them.

encapsulation.rb
class Account
  def initialize(balance)
    @balance = balance
  end

  def transfer_to(other, amount)
    withdraw(amount)
    other.deposit(amount)
  end

  def balance_report
    "Balance: #{format_money(@balance)}"
  end

  protected

  def deposit(amount)
    @balance += amount
  end

  private

  def withdraw(amount)
    @balance -= amount
  end

  def format_money(value)
    "$#{value}"
  end
end

a = Account.new(100)
b = Account.new(20)
a.transfer_to(b, 30)
puts a.balance_report
puts b.balance_report

begin
  a.withdraw(10)
rescue NoMethodError => e
  puts "Error: #{e.message[0, 40]}"
end

transfer_to calls the private withdraw on itself, which is allowed because there is no explicit receiver. It calls the protected deposit on another Account, which is allowed because both objects are instances of the same class. Calling a.withdraw from outside raises NoMethodError, and the example prints the start of that message. See the private keyword, protected keyword, and public keyword pages.

$ ./encapsulation.rb
Balance: $70
Balance: $50
Error: private method `withdraw' called for #<A

to_s and inspect Overrides

The to_s method controls how an object is converted to a string for puts and interpolation, while inspect controls the output of p and debugging views. Overriding both gives clean, readable output for custom classes.

to_s_inspect.rb
class Point
  attr_reader :x, :y

  def initialize(x, y)
    @x = x
    @y = y
  end

  def to_s
    "(#{@x}, #{@y})"
  end

  def inspect
    "#<Point x=#{@x} y=#{@y}>"
  end
end

pt = Point.new(3, 4)
puts pt
p pt
puts "Point: #{pt}"
puts [pt].inspect

puts pt and string interpolation call to_s. p pt calls inspect. Arrays call inspect on their elements, which is why [pt].inspect uses the custom format. The attr_reader call provides read-only access to x and y.

$ ./to_s_inspect.rb
(3, 4)
#<Point x=3 y=4>
Point: (3, 4)
[#<Point x=3 y=4>]

Duck Typing

Ruby does not check the type of an object, only whether it responds to the messages sent to it. If an object implements speak, it can be used wherever a speaker is expected, regardless of its class. This is called duck typing.

duck_typing.rb
class Duck
  def speak
    "Quack"
  end
end

class Robot
  def speak
    "Beep boop"
  end
end

class Stone
  def throw_it
    "thud"
  end
end

def make_it_speak(thing)
  puts thing.speak
rescue NoMethodError
  puts "#{thing.class} cannot speak"
end

[Duck.new, Robot.new, Stone.new].each do |thing|
  make_it_speak(thing)
end
puts Duck.new.respond_to?(:speak)
puts Stone.new.respond_to?(:speak)

make_it_speak accepts any object. Duck and Robot both respond to speak, so they work without sharing a common superclass. Stone does not, so the NoMethodError is rescued. respond_to? lets code check capability before calling a method.

$ ./duck_typing.rb
Quack
Beep boop
Stone cannot speak
true
false

Comparable and Enumerable

Two standard modules add behavior to your classes with little code. Including Comparable and defining <=> gives you <, >, between? and similar methods. Including Enumerable and defining each gives you map, count and many others. This page only introduces them; see the Enumerable and Comparable page for details.

Summary

Ruby combines classes for state and behavior, single inheritance with super, modules for shared mixins, and visibility modifiers for encapsulation. Duck typing lets objects be used by their behavior rather than their class, so well-designed Ruby code relies on clear method interfaces.

List all Ruby tutorials.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.