Super in Ruby

In object oriented programming languages, inheritance is a great concept to use. It is not just about code reuse, but about design. However, we can reuse code when using inheritance, but we need to know how it works within our language and how classes and methods interact in order to take benefit of it.

Methods in subclasses and superclasses

When executing an object’s method in Ruby, the interpreter search for this method in the object’s class definition, if it is found, then it is executed, otherwise it search in the object’s class’s superclass, and so on. Example:

class Person
  def sleep
    puts "Sleeping..."
  end

  def eat
    puts "Eating..."
  end

  def run
    puts "Running..."
  end
end

class Employee < Person
  def work
    puts "Working..."
  end
end

Executing:

eployee = Employee.new
employee.work
# Working...
employee.sleep
# Sleeping...

Both methods were executed, but work was taken from the Employee class, while sleep was taken form the Person class.

When a method is overridden, the superclass’s method is hidden and the subclass’s one is the only one that can be executed.

class Person
  def sleep
    puts "Sleeping..."
  end

  def eat
    puts "Eating..."
  end

  def run
    puts "Running..."
  end
end

class Employee < Person
  def work
    puts "Working..."
  end

  def sleep
    puts 'sleeping at work.'
  end
end

Here, the work method is defined in the Person class, but it is also defined in the Employee class, so when is executed from the employee object, the one executed is in the Employee class.

employee = Employee.new
employee.sleep
# sleeping at work.

Calling a superclass’s method from a subclass

It is possible that the Employee’s sleep method needs to reuse code from Person’s sleep. Instead of copy-paste, we can simply call the superclass’s method. But there is a gotcha: we can not call sleep directly because that would provokes a recursive call over itself. Example:

class Employee < Person
  def work
    puts "Working..."
  end

  def sleep
    sleep # try to call the superclass's method
    puts "at work"
  end
end

Trying execute the sleep method will generate a stack level too deep (SystemStackError). In these cases, we must use the keyword super.

class Employee < Person
  def work
    puts "Working..."
  end

  def sleep
    super # try to call the superclass's method
    puts "at work"
  end
end

Thus, the Employee’s sleep method can use the functionality of the Person’s sleep method.

employee = Employee.new
employee.sleep
# Sleeping...
# at work

How to call super with parameters

Now, this method has a particular behavior that is necessary to know in order to avoid some mistakes.

If the superclass’s method receives parameters, is possible to pass them explicitly or implicitly. Explicitly means we have to specify the parameters during the calling; implicitly means we can just call super with no parameters. In the following example we can see both behaviors. slepp and play receives two parameters, both in superclass as in subclass. However, calling super in the first method is made with parameters and in the second method is made without parameters. Both works correctly.

class Person
  def sleep(hours, minutes)
    puts "Sleeping... #{hours} hours, #{minutes} minutes"
  end

  def eat
    puts "Eating..."
  end

  def run
    puts "Running..."
  end

  def play(hours, minutes)
    puts "Playing... #{hours} ours, #{minutes} minutes"
  end
end

class Employee < Person
  def work
    puts "Working..."
  end

  def sleep(hours, minutes)
    super(hours, minutes)
    puts "... at work"
  end

  def play(hours, minutes)
    super
    puts "... at work"
  end
end

Executing, we have this result:

employee = Employee.new
employee.sleep(2, 30) 
# Sleeping... 2 hours, 30 minutes
# ... at work
employee.play(3, 10) 
# Playing... 3 hours, 10 minutes
# ... at work

Calling super with no parameters provokes ruby to include them automatically.

Different number of parameters

Ideally, we should follow the Liskov substitution principle and therefor the scenario we are going to see would never happen. However, in the real word, it can happen (sometimes, more often than we want), so we have to contemplate it. If the superclass’s method receives a different number of parameters, an error will be raised.

class Person

  # ...

  def rest(hours, minutes)
    puts "Resting... #{hours} hours, #{minutes} minutes"
  end
end

class Employee < Person

  # ...

  def rest(minutes)
    super
    puts "... at work"
  end
end

Executing:

employee = Employee.new
employee.rest(90)
# ArgumentError: wrong number of arguments (1 for 2)

The problem was Ruby detected the call to super with no parameters and, automatically, adds them. The problem was that the number of parameters doesn’t match. In these cases, we have to call super specifying the parameters explicitly.

class Person

  # ...

  def rest(hours, minutes)
    puts "Resting... #{hours} hours, #{minutes} minutes"
  end
end

class Employee < Person

  # ...

  def rest(minutes)
    super(minutes / 60, minutes % 60)
    puts "... at work"
  end
end

Execution:

employee = Employee.new
employee.rest(90)
# Resting... 1 hours, 30 minutes
# ... at work

Superclass’s method with no parameters and subclass’s methods with parameters

One more scenario. What happen when the superclass’s method doesn’t receive parameters but the subclass’s one does?

class Person

  # ...

  def program
    puts "Programming..."
  end
end

class Employee < Person

  # ...

  def program(project)
    super
    puts "... project #{project}"
  end
end

We get the following error:

employee = Employee.new
employee.program("Personal")
# person.rb:5:in `program': wrong number of arguments (1 for 0) (ArgumentError)
#        from person.rb:15:in `program'

The error is that when executing super, Ruby adds the parameters automatically and that provokes the fail because the superclass’s method doesn’t receive any parameters. For these cases, the solution is to call the method with the parenthesis, like if we were including parameters, but leaving them empty. This way, Ruby understand that it should not add parameters automatically.

class Person

  # ...

  def program
    puts "Programming..."
  end
end

class Employee < Person

  # ...

  def program(project)
    super()
    puts "... project #{project}"
  end
end

Execution:

employee = Employee.new
employee.program("Personal")
# Programming...
# ... project Important

The initialize method

The same principle applies for the constructor.

class Person

  def initialize
    # code to initialize a person
  end

  # ...

end

class Employee < Person

  attr_reader :employee_number

  def initialize(employee_number)
    super()
    @employee_number = employee_number
  end

  # ...

end

Final thoughts

Using inheritance in Ruby implies knowing how the methods interact inside the hierarchy, specially when we want to reuse the behaviour it provides. Using super is essential in these cases, and knowing how it works allows us to use the language’s properties in a correct way.

comments powered by Disqus