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.
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.
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.
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.
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.
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.
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.