Ruby Inheritance — Superclass < Operator super Keyword and Ancestors Chain
In this tutorial, you will learn about Ruby Inheritance. We cover key concepts, practical examples, and best practices to help you master this topic.
Ruby inheritance uses the < operator to create class hierarchies, super to invoke parent class methods, and the ancestors method to inspect the complete method lookup chain.
What You'll Learn
- Creating class hierarchies with the < operator
- Using super to call parent methods
- Overriding and extending inherited methods
- The ancestor chain and method lookup algorithm
Why It Matters
Inheritance enables code reuse and establishes logical relationships. Durga Antivirus Pro uses inheritance for specialized scanners (FileScanner, NetworkScanner, EmailScanner) that share base functionality. Doda Browser uses inheritance for page types (WebPage, PDFPage, ImagePage). Understanding inheritance hierarchies helps you design clean, maintainable object models.
Real-World Use
Rails models inherit from ApplicationRecord, which inherits from ActiveRecord::Base. Controllers inherit from ApplicationController. This gives every model/controller access to shared functionality like CRUD operations, parameter handling, and callbacks.
flowchart LR
A["Inheritance"] --> B["Base Class"]
B --> C["Subclass"]
C --> D["Method Override"]
D --> E["super"]
E --> F["Ancestors"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#dbeafe,stroke:#2563eb,color:#1e40af
style F fill:#f1f5f9,stroke:#94a3b8,color:#64748b
Basic Inheritance
class Animal
def speak
"Some sound"
end
def move
"Moving..."
end
end
class Dog < Animal
def speak
"Woof!"
end
end
class Cat < Animal
def speak
"Meow!"
end
end
dog = Dog.new
cat = Cat.new
puts dog.speak # Woof!
puts cat.speak # Meow!
puts dog.move # Moving... (inherited from Animal)
Using Super
super calls the parent class's implementation of the same method:
class Animal
def initialize(name)
@name = name
end
def describe
"I am an animal named #{@name}"
end
end
class Dog < Animal
def initialize(name, breed)
super(name) # Calls Animal's initialize
@breed = breed
end
def describe
"#{super} and I am a #{@breed} dog"
end
end
dog = Dog.new("Rex", "German Shepherd")
puts dog.describe
# I am an animal named Rex and I am a German Shepherd dog
Super with No Arguments
Without explicit arguments, super forwards the current method's arguments:
class Parent
def greet(name)
"Parent says hello to #{name}"
end
end
class Child < Parent
def greet(name)
super # Automatically passes 'name'
end
end
puts Child.new.greet("Alice")
# Parent says hello to Alice
Super with Empty Parentheses
super() calls the parent method with NO arguments:
class Parent
def greet(name = "World")
"Hello #{name}"
end
end
class Child < Parent
def greet(name)
super() # Calls Parent#greet with no arguments → uses default
end
end
puts Child.new.greet("Alice")
# Hello World
Super with Specific Arguments
class Parent
def calculate(a, b, c)
a + b + c
end
end
class Child < Parent
def calculate(a, b, c)
super(a, b, 0) # Override third argument
end
end
puts Child.new.calculate(1, 2, 3) # 3 (1+2+0)
Method Overriding
class Vehicle
def initialize(make, model)
@make = make
@model = model
end
def start
"Engine starting"
end
def fuel_type
"Unknown"
end
end
class ElectricCar < Vehicle
def start
"Electric motor humming"
end
def fuel_type
"Electricity"
end
def battery_range
"300 miles"
end
end
tesla = ElectricCar.new("Tesla", "Model 3")
puts tesla.start # Electric motor humming
puts tesla.fuel_type # Electricity
puts tesla.battery_range # 300 miles
Overriding and Extending
class Logger
def log(message)
puts "[#{Time.now}] #{message}"
end
end
class FileLogger < Logger
def initialize(filename)
@file = File.open(filename, "a")
end
def log(message)
super(message) # Also prints to console
@file.puts("[#{Time.now}] #{message}")
@file.flush
end
def close
@file.close
end
end
logger = FileLogger.new("app.log")
logger.log("Application started")
logger.close
The Ancestors Chain
class Grandparent
end
class Parent < Grandparent
end
class Child < Parent
end
puts Child.ancestors.inspect
# [Child, Parent, Grandparent, Object, Kernel, BasicObject]
How Method Lookup Works
class A
def who
"A"
end
end
module M
def who
"M"
end
end
class B < A
include M
def who
"B"
end
end
obj = B.new
puts obj.who # "B"
# Lookup: obj → B → M → A → Object → Kernel → BasicObject
Inheritance and Class Variables
Class variables are shared across the hierarchy:
class Base
@@count = 0
def self.count
@@count
end
def increment
@@count += 1
end
end
class A < Base
end
class B < Base
end
A.new.increment
puts Base.count # 1
puts A.count # 1
puts B.count # 1 (shared!)
This is often surprising. Use class instance variables instead:
class Base
@count = 0
class << self
attr_accessor :count
end
end
class A < Base
@count = 10
end
class B < Base
@count = 20
end
puts Base.count # 0
puts A.count # 10
puts B.count # 20
Abstract Base Classes
Ruby doesn't have abstract classes, but you can simulate them:
class Shape
def area
raise NotImplementedError, "Subclass must implement area"
end
def perimeter
raise NotImplementedError, "Subclass must implement perimeter"
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
circle = Circle.new(5)
puts circle.area.round(2) # 78.54
puts circle.perimeter.round(2) # 31.42
Inheritance vs Composition
While Ruby supports inheritance, composition is often preferred:
# Inheritance
class Car < Vehicle
# Car is a Vehicle
end
# Composition
class Car
def initialize
@engine = Engine.new # Car has an Engine
@wheels = [Wheel.new, Wheel.new, Wheel.new, Wheel.new]
end
end
The is_a? and kind_of? Methods
class Animal; end
class Dog < Animal; end
class Cat < Animal; end
dog = Dog.new
cat = Cat.new
puts dog.is_a?(Dog) # true
puts dog.is_a?(Animal) # true
puts dog.is_a?(Object) # true
puts cat.is_a?(Dog) # false
puts dog.kind_of?(Animal) # true (alias for is_a?)
Common Mistakes
1. Forgetting super in Overridden Initialize
class Animal
def initialize(name)
@name = name
end
end
class Dog < Animal
def initialize(name, breed)
# Forgot super(name) — @name never set!
@breed = breed
end
end
2. Confusing super with super()
class Parent
def greet(name = "World")
"Hello #{name}"
end
end
class A < Parent
def greet(name)
super # Forwards name argument
# "Hello Alice"
end
end
class B < Parent
def greet(name)
super() # Calls with NO arguments → uses default
# "Hello World"
end
end
3. Overusing Inheritance
# Deep hierarchies are hard to maintain
class A < B
end
class B < C
end
class C < D
end
# Prefer composition: class A; def initialize; @b = B.new; end; end
4. Class Variable Surprises
class Base
@@values = []
end
class A < Base
@@values << "A"
end
class B < Base
@@values << "B"
end
puts Base.class_variable_get(:@@values).inspect
# ["A", "B"] — shared across hierarchy!
5. Not Using super in Hook Methods
module Trackable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def inherited(subclass)
puts "#{subclass} inherits from #{self}"
super # Don't forget super!
end
end
end
Practice Questions
1. How do you create a class that inherits from another class?
Use the < operator: class Dog < Animal. Dog is the subclass, Animal is the superclass.
2. What does super do?
super calls the parent class's method with the same name. Without arguments, it forwards the current method's arguments. super() calls with no arguments. super(a, b) calls with specific arguments.
3. What is the method lookup chain in Ruby?
When calling a method, Ruby searches: the object's class, prepended modules, the class, included modules, the superclass, the superclass's modules, and so on up to BasicObject. Class.ancestors shows the chain.
4. What's the difference between class variables (@@) and class instance variables (@)?
Class variables are shared across the entire hierarchy (parent and all subclasses). Class instance variables (@ in the class body) are private to that specific class and not shared with subclasses.
Challenge: Create a hierarchy: Mammal → Animal → Dog/Cat. Add a class-level species tracking using class instance variables (not class variables).
Solution
class Mammal
@species = "Unknown"
class << self
attr_reader :species
end
def initialize(name)
@name = name
end
def describe
"#{@name} is a #{self.class.species}"
end
end
class Dog < Mammal
@species = "Canis familiaris"
end
class Cat < Mammal
@species = "Felis catus"
end
dog = Dog.new("Rex")
cat = Cat.new("Whiskers")
puts dog.describe # Rex is a Canis familiaris
puts cat.describe # Whiskers is a Felis catus
FAQ
{{< faq question="Can a Ruby class inherit from multiple classes?" >}} No. Ruby supports single inheritance — a class can have only one direct parent. Multiple inheritance is achieved through modules (mixins) which don't have the diamond problem. {{< /faq >}}
{{< faq question="What happens if a method isn't found in the ancestor chain?" >}}
Ruby calls method_missing on the object. If that's not defined, it raises a NoMethodError. The method_missing approach is used by ActiveRecord for dynamic finders.
{{< /faq >}}
{{< faq question="Can I change which class a method is defined in at runtime?" >}}
Yes. Ruby is dynamic. You can define methods on any class at any time. This is called open classes or monkey patching. Use prepend or refinements to do this safely.
{{< /faq >}}
{{< faq question="What is the root class of all Ruby objects?" >}}
BasicObject is the root. Object inherits from BasicObject, and all objects inherit from Object. Kernel is a module included by Object that provides methods like puts, print, and require.
{{< /faq >}}
{{< faq question="Is inheritance or composition preferred in Ruby?" >}} Composition is generally preferred (has-a over is-a). Ruby makes composition easy with modules. Deep inheritance hierarchies (more than 2-3 levels) become hard to maintain. Use modules for shared behavior and composition for related objects. {{< /faq >}}
Try It Yourself
# inheritance_demo.rb
class Employee
attr_reader :name, :salary
def initialize(name, salary)
@name = name
@salary = salary
end
def work
"Doing general work"
end
def bonus
salary * 0.1
end
def details
"#{name} earns $#{salary}"
end
end
class Manager < Employee
def initialize(name, salary, team_size)
super(name, salary)
@team_size = team_size
end
def work
"Managing a team of #{@team_size}"
end
def bonus
super + salary * 0.1 # Manager gets double bonus
end
def details
"#{super}, manages #{@team_size} people"
end
end
emp = Employee.new("Alice", 50000)
mgr = Manager.new("Bob", 80000, 5)
puts emp.work # Doing general work
puts mgr.work # Managing a team of 5
puts emp.details # Alice earns $50000
puts mgr.details # Bob earns $80000, manages 5 people
puts emp.bonus # 5000.0
puts mgr.bonus # 16000.0
Expected output:
Doing general work
Managing a team of 5
Alice earns $50000
Bob earns $80000, manages 5 people
5000.0
16000.0
What's Next
Now that you understand inheritance, learn about blocks and procs — Ruby's powerful closure mechanism for passing code as arguments.
| Topic | Description | Link |
|---|---|---|
| Ruby Blocks & Procs | Blocks, yield, Proc.new, call | {{< ref "12-blocks-procs" >}} |
| Ruby Lambdas | ->, lambda, arity, closure | {{< ref "13-lambdas" >}} |
| Python Classes | Compare Python class inheritance | Python |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro