---
title: 'Inheritance and `super`'
source: 'https://academia.sh/en/courses/object-orientation-and-metaprogramming/inheritance-and-super'
course: 'Object Orientation and Metaprogramming'
language: en
updated: '2026-10-21T05:03:42+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Measure that an empty subclass takes its method from an ancestor, using superclass, ancestors and instance_methods'
    - 'Show with super_method that a super call in an overriding method descends to the definition in the superclass'
    - 'Tell apart the argument difference between bare super and super(...) through ArgumentError'
---

# Inheritance and `super`

An empty subclass taking a method from its superclass; the ancestor chain with superclass and ancestors; super in an overriding method; super inside initialize; the difference between bare super and super(...).

The previous lesson said that `balance_text` is written in the `Account` class, yet an empty subclass named `TermDeposit` can also call the same method and get an answer. There is not a single line of definition in the subclass. Who brought the method there, and what changes when the same method is rewritten in the subclass?

The author of this lesson is the ancestor: the definition sits away from the receiver, in a superclass higher in the chain. The lesson first measures an empty subclass, then looks at how overriding with `super` makes a definition change hands.

## Where an Empty Subclass Gets Its Method

The line `class TermDeposit < Account` makes `Account` the superclass of `TermDeposit`. The methods of the superclass are open to the objects of the subclass. This relation is called inheritance (M08/K02). The file below rebuilds the `superclass` observation of the core for `TermDeposit`. The file `author.rb` is the rule given in the first lesson and is loaded unchanged.

```ruby
# author.rb — a helper that classifies who wrote a method

# Classification asks in order: is there a definition in the receiver's chain at all,
# then does the receiver's own class carry it, then how was the definition produced.
def author(receiver, name)
  chain = receiver.singleton_class
  unless chain.method_defined?(name) || chain.private_method_defined?(name)
    return ["nobody", "no definition"]
  end

  definition = chain.instance_method(name)
  owner = definition.owner
  place = owner == chain ? "singleton class" : owner.to_s
  return ["ancestor", place] unless owner == receiver.class || owner == chain

  iseq = RubyVM::InstructionSequence.of(definition)
  return ["code", "#{place}, no ruby body"] if iseq.nil?
  return ["code", "#{place}, block body"] if iseq.label.start_with?("block")

  ["programmer", "#{place}, def body"]
end
```

`author.rb` adds nothing new in this lesson; it stands as the source. The measurement is in the file that calls it.

```ruby
# superclass.rb — an empty subclass takes the method from an ancestor
require_relative "author"

class Account
  def initialize(owner, balance)
    @owner = owner
    @balance = balance
  end

  def balance_text = "#{@owner} #{@balance}"
end

class TermDeposit < Account
end

term = TermDeposit.new("tom", 50)
p term.balance_text
p author(term, :balance_text)
p TermDeposit.instance_methods(false)
```

```
"tom 50"
["ancestor", "Account"]
[]
```

`term.balance_text` gave `tom 50`. `author` found the ancestor author, with `Account` as the evidence: the owner of the definition is not `TermDeposit`, the receiver's class, but `Account`. `TermDeposit.instance_methods(false)` returned an empty array, so `TermDeposit` carries no method of its own. The call `TermDeposit.new("tom", 50)` worked too, because `initialize` also came from `Account` by the same route. Even when the subclass is empty, the object carries the complete behavior of an `Account`.

**CM4.** In this lesson the "superclass" is the class written to the right of the `<` sign in the line `class TermDeposit < Account`; the "subclass" is the one written on the left. An "ancestor" is every class or module that comes after a class in its chain. The rule counts the author as an ancestor when the owner of the method is not the receiver's class.

## The Ancestor Chain

A superclass points at a single class. But `Account` has a superclass as well, and the chain continues from there. The file below asks the chain in two ways: `superclass` gives one step, `ancestors` gives the whole order.

```ruby
# chain.rb — superclass and ancestors
module Reportable
  def report = "report: #{balance_text}"
end

class Account
  include Reportable
end

class TermDeposit < Account
end

p TermDeposit.superclass
p Account.superclass
p TermDeposit.ancestors.take(5)
p Integer.ancestors.take(4)
```

```
Account
Object
[TermDeposit, Account, Reportable, Object, Kernel]
[Integer, Numeric, Comparable, Object]
```

`TermDeposit.superclass` gave `Account`, and `Account.superclass` gave `Object`. The `ancestors` list carries classes and modules together: the receiver's class at the very front, then `Account`, then the `Reportable` module that `Account` mixed in, then `Object` and `Kernel`. The place of modules in the chain is the subject of the next lesson; here it is enough to notice that `Reportable` stands between `Account` and `Object`. The core classes have a chain in the same way: `Integer` links first to `Numeric`, then to the `Comparable` module, then to `Object`. So the `between?` in the call `1.between?(0, 2)` is also a method written not by `Integer` but by an ancestor in the chain.

## super in an Overriding Method

A subclass can rewrite a method of the superclass under the same name. This operation is called overriding. Overriding does not delete the definition of the superclass; because the subclass comes first in the receiver's chain, the message reaches it first. `super`, in turn, descends from this definition to the next definition of the same name in the chain. The file below rewrites `balance_text` in the class `TermDeposit2` and leaves half of the body to `super`.

```ruby
# overriding.rb — super descends to the next definition in the chain
class Account
  def initialize(owner, balance)
    @owner = owner
    @balance = balance
  end

  def balance_text = "#{@owner} #{@balance}"
end

class TermDeposit2 < Account
  def balance_text = "term #{super}"
end

t2 = TermDeposit2.new("tom", 50)
puts t2.balance_text
p t2.method(:balance_text).owner
p t2.method(:balance_text).super_method.owner
p t2.method(:balance_text).super_method.super_method
```

```
term tom 50
TermDeposit2
Account
nil
```

The output `term tom 50` is the combination of two parts: `TermDeposit2` wrote the prefix `term `, and `Account` wrote the `tom 50` part through the `super` call. `method(:balance_text).owner` gave `TermDeposit2`, and `super_method.owner` gave `Account`. `super_method` gives the next definition in the chain as a method object. Because no ancestor that defines `balance_text` remained after `Account`, `super_method.super_method` returned `nil`. So a `super` call is a hop that keeps walking down the chain, but only as long as there is a definition of the same name.

## Telling the Owner from the Copy

`TermDeposit.instance_methods(false)` came out empty, yet `TermDeposit` still answers `balance_text`. Two questions have to be kept apart: is the method written in this class, or can it be called by the objects of this class? `instance_methods(false)` asks only the first. `method_defined?` asks the second, and `instance_method(...).owner` says where the definition actually sits. The file below puts the three questions to the same class, and then to the overriding `TermDeposit2`.

```ruby
# owner_question.rb — being written and being callable are separate questions
class Account
  def initialize(owner, balance)
    @owner = owner
    @balance = balance
  end

  def balance_text = "#{@owner} #{@balance}"
end

class TermDeposit < Account
end

class TermDeposit2 < Account
  def balance_text = "term #{super}"
end

p TermDeposit.method_defined?(:balance_text)
p TermDeposit.instance_methods(false)
p TermDeposit.instance_method(:balance_text).owner
p TermDeposit2.instance_methods(false)
p TermDeposit2.instance_method(:balance_text).owner
p Account.instance_method(:balance_text).owner
```

```
true
[]
Account
[:balance_text]
TermDeposit2
Account
```

`TermDeposit.method_defined?(:balance_text)` gave true, while `TermDeposit.instance_methods(false)` stayed empty; the two do not contradict. The first looks at the whole chain, the second only at the body of the class. `instance_method(...).owner` gave `Account` for `TermDeposit`: the definition that runs when the objects of `TermDeposit` call it is written in `Account`, and there is no copy in `TermDeposit`. For `TermDeposit2` the picture changes: `instance_methods(false)` now carries `balance_text`, and the `owner` is `TermDeposit2`. The definition of `Account` stays in place and still comes out with `Account` as its owner. Overriding is not an overwrite; it puts a second definition at the front of the chain.

## super Inside initialize

`initialize` is also a method and can be overridden in a subclass. While the subclass sets up its own extra variable, it asks with `super` for the superclass to set up its variables. The file below defines two subclasses; the `initialize` methods of both carry the same signature, but `super` is written differently.

```ruby
# initialize_super.rb — super(...) and bare super
class Account
  def initialize(owner, balance)
    @owner = owner
    @balance = balance
  end

  def balance_text = "#{@owner} #{@balance}"
end

class Interest < Account
  def initialize(owner, balance, rate)
    super(owner, balance)
    @rate = rate
  end
end

class Faulty < Account
  def initialize(owner, balance, rate)
    super
    @rate = rate
  end
end

p Interest.new("amy", 100, 5).instance_variables
begin
  Faulty.new("amy", 100, 5)
rescue ArgumentError => e
  puts e.class
end
```

```
[:@owner, :@balance, :@rate]
ArgumentError
```

`Interest` passed two arguments to the superclass with `super(owner, balance)`, and `instance_variables` listed three variables in order: the two variables of the superclass, then the `@rate` of the subclass. In the `Faulty` class a bare `super` was written and an `ArgumentError` arose. A bare `super` passes on all the arguments of the calling method unchanged; because `Faulty#initialize` took three arguments, three arguments went to `Account#initialize`, which expects two.

**CM5.** In this lesson `super(...)` means the call with parentheses, and "bare `super`" means the call written without parentheses and without arguments. The call with parentheses passes to the superclass only the arguments that are written; the bare call passes the method's own arguments as they are. When the signature of the subclass differs from that of the superclass, a bare `super` may pass the wrong arguments; in that case the call with parentheses is written.

The difference between the forms with and without parentheses is completed by a third spelling: `super()` with empty parentheses. A bare `super` passes the method's own arguments, while `super()` passes no argument at all. The difference becomes visible only when the method in the superclass has a parameter with a default value. The file below puts the three forms side by side in three subclasses.

```ruby
# super_forms.rb — bare super, super() and super(argument)
class Account
  def initialize(owner, balance)
    @owner = owner
    @balance = balance
  end

  def label(prefix = "account") = "#{prefix}: #{@owner}"
end

class Bare < Account
  def label(prefix = "bare") = super
end

class Empty < Account
  def label(prefix = "empty") = super()
end

class Explicit < Account
  def label(prefix = "explicit") = super("fixed")
end

puts Bare.new("tom", 1).label
puts Empty.new("tom", 1).label
puts Explicit.new("tom", 1).label
puts Bare.new("tom", 1).label("manual")
```

```
bare: tom
account: tom
fixed: tom
manual: tom
```

The bare `super` carried the `prefix` parameter of the subclass to the superclass; the default `account` in the superclass was never used. `super()` passed nothing, so the superclass returned to its own default. `super("fixed")` passed the written value and ignored the parameter of the subclass. The fourth line shows that what the bare `super` passes is the current value of the parameter: when `manual` was given to the call, `manual` went to the superclass. This is what makes the bare `super` useful, and it is also what makes it dangerous; the moment the signature of the subclass departs from that of the superclass, what is passed departs as well.

What happens when a `super` call finds no definition of the same name in the chain? The measurement below asks this.

```ruby
# orphan_super.rb — super with no definition in the ancestors
class Account
  def initialize(owner, balance)
    @owner = owner
    @balance = balance
  end
end

class Orphan < Account
  def missing = super
end

begin
  Orphan.new("a", 1).missing
rescue NoMethodError => e
  puts e.class
end
```

```
NoMethodError
```

`super` found no definition named `missing` in the chain and raised `NoMethodError`. So `super` is not magic but a search query: it looks for the next definition of the same name in the chain, and if it does not find one, it meets the error that meets every message.

The chain is not limited to two steps. Each subclass descends to the next with its own `super` call, and the calls return in order. The file below builds a chain of three classes and walks the chain from start to end with `super_method`, collecting the owners.

```ruby
# three_steps.rb — the super_method chain is walked to its end
class Account
  def initialize(owner, balance)
    @owner = owner
    @balance = balance
  end

  def balance_text = "#{@owner} #{@balance}"
end

class TermDeposit2 < Account
  def balance_text = "term #{super}"
end

class Gold < TermDeposit2
  def balance_text = "gold #{super}"
end

g = Gold.new("tom", 50)
puts g.balance_text

owners = []
current = g.method(:balance_text)
while current
  owners << current.owner
  current = current.super_method
end
p owners
```

```
gold term tom 50
[Gold, TermDeposit2, Account]
```

The text was assembled from the innermost definition outward: `Account` wrote the `tom 50` part, `TermDeposit2` wrote the `term ` prefix, and `Gold` wrote the `gold ` prefix. The definition closest to the receiver is the first to run, yet it is the one waiting for the next in the middle of its body. The `super_method` loop gave the owners in the same order and stopped when it returned `nil`. This confirms that a `super` call is not an invisible mechanism but a path read over the order of `ancestors`.

## The Chain Is Searched at Call Time

When the `TermDeposit` class was defined, it did not copy the definitions of `Account` into itself; when a call arrives, it looks at the chain. This has an observable consequence: a method added to `Account` after `TermDeposit` was set up can be called at once on a `TermDeposit` object as well. The file below puts the same question to the same object, before and after `Account` is reopened.

```ruby
# late_addition.rb — a method an ancestor gains later is visible in the subclass
class Account
  def initialize(owner, balance)
    @owner = owner
    @balance = balance
  end
end

class TermDeposit < Account
end

t = TermDeposit.new("tom", 50)
p t.respond_to?(:interest)

class Account
  def interest = @balance / 10
end

p t.respond_to?(:interest)
p t.interest
p TermDeposit.instance_methods(false)
```

```
false
true
5
[]
```

The first `respond_to?` gave false, and after `Account` was reopened it gave true, and `t.interest` returned `5`. In the meantime `t` was never rebuilt. `TermDeposit.instance_methods(false)` is still empty: `interest` was not written to the subclass; it only became a definition of an ancestor in the chain. The definition that is searched for is found according to the current state of the chain at call time.

**CM13.** In this lesson the "chain" is the search order that starts at the receiver's class and runs as far as `BasicObject`. The members of the chain are not copied into the class; the search is made from scratch on every call. A method an ancestor gains later is therefore visible in the existing objects of the subclass too.

## The Bounding Measurement

`TermDeposit` wrote nothing, and its author came out as an ancestor. `TermDeposit2` took half of the body from the ancestor and wrote the other half itself. How does the rule classify this second one?

```ruby
# half_ancestor.rb — an overriding definition that calls super counts as the programmer
require_relative "author"

class Account
  def initialize(owner, balance)
    @owner = owner
    @balance = balance
  end

  def balance_text = "#{@owner} #{@balance}"
end

class TermDeposit < Account
end

class TermDeposit2 < Account
  def balance_text = "term #{super}"
end

p author(TermDeposit.new("tom", 50), :balance_text)
p author(TermDeposit2.new("tom", 50), :balance_text)
```

```
["ancestor", "Account"]
["programmer", "TermDeposit2, def body"]
```

The result was an ancestor for `TermDeposit` and the programmer for `TermDeposit2`. Although the body of `TermDeposit2` took half from the ancestor, the rule does not measure that. The rule counts the first definition found in the chain and compares the owner of that definition with the receiver's class; it does not look at the rest of the chain.

**CM6.** In this lesson the "first definition found" is the first definition met in the search order that starts at the receiver. `instance_method(name)` gives it. The rule does not see that a `super` call descends to another definition further along the chain, because it does not look at the body of the first definition.

The consequence runs both ways. Writing a single `def` in the subclass turns the author from an ancestor into the programmer; how much of the body is handed over to the superclass does not change. On the other hand, the body of a method whose author is the programmer need not belong wholly to that programmer. The author category says the place of the first definition in the chain, not the source of the body.

## Summary

- An empty subclass takes its methods from the superclass: `TermDeposit.instance_methods(false)` is empty and `TermDeposit.new("tom", 50).balance_text` comes from an ancestor.
- `superclass` gives one step and `ancestors` gives the whole chain; `TermDeposit.ancestors.take(5)` shows the order of subclass, superclass, mixed-in module, `Object` and `Kernel`.
- Overriding does not delete the definition of the superclass; `super` descends to the next definition of the same name in the chain, and `super_method.owner` gives the owner of that definition.
- Inside `initialize`, `super(owner, balance)` passes only the arguments that are written; in the same signature a bare `super` passes the arguments unchanged and so raises `ArgumentError`.
- The author of this lesson is the ancestor: in the `superclass` row `Account` wrote `balance_text`, and `TermDeposit` wrote nothing.
- A `super` with no definition in the ancestors raises `NoMethodError`; in an overriding method that calls `super` the author comes out as the programmer, because the rule counts the first definition found.

## Next Step

The method `report` was never written in the `Account` class, yet `account.report` worked and gave `report: amy 100`. The method comes neither from a superclass nor from the body of the class itself. How does `Reportable`, which stands between `Account` and `Object` in the chain, carry that method? The next lesson, Modules and Mixins, measures this.
