Learning about Ruby’s Super (OOP)

So often we are overly hasty to move on to the next section; we rarely stop to take a breather. So take a moment to let your mind go blank. Remember, deep breaths.

In this article, we are going to analyze super, a topic covered in the OOP section of Launch School.

In one sentence, super is a built-in function in Ruby that allows us to invoke methods that would otherwise be replaced by method overriding during single inheritance.

What does that all mean?

We will start small.

Take a look at the example below.

class Programmer

def write_code
    "This code is generic and quite possibly mediocre"
  end
end

class Rubyist < Programmer

  def write_code
    "This code follows the principles of OO Design"
  end
end

The above example showcases method overriding. As we can see, there are two instance methods called #write_code.

If we were to instantiate an object from the Rubyist class and invoke the #write_code method, we would see the return value of the Rubyist instance method only. Example below:

class Programmer

  def write_code
    "This code is generic and quite possibly mediocre."
  end
end

class Rubyist < Programmer
 def write_code
    "This code follows the principles of OO Design."
  end
end

steve = Rubyist.new

puts steve.write_code 
# => "This code follows the principles of OO Design."

Unlike, some other languages, Ruby is single inheritance, which means each subclass can only inherit from one superclass using the < character.

In the example above, we see that invoking the #write_code instance method on an instance of the Rubyist class, stored in the local variable steve, only calls the instance method in the Rubyist class, despite inheritance with the above class Programmer.

If we wanted to run the instance method in the superclass Programmer, we would need to use super.

class Programmer

  def write_code
    "This code is generic and quite possibly mediocre"
  end
end

class Rubyist < Programmer

  def write_code
    super + " " + "This code follows the principles of OO Design"
  end
end

steve = Rubyist.new

puts steve.write_code 
# => "This code is generic and quite possibly mediocre. This code follows the principles of OO Design."

(As we can see, now when we call the #write_code method, we invoke both classes’ instance methods.)

Super (with arguments)

Super is often found inside the #initialize instance method, which gets called when we instantiate a new object using the built-in class method #new.

Take a look at this example:

class Programmer
  def initialize(name, experience)
    @name = name
    @experience = experience
  end

  def write_code
    "This code is generic and quite possibly mediocre"
  end
end

class Rubyist < Programmer
  def initialize(name, experience)
    super
  end

  def write_code
    super + " " + "This code follows the principles of OO Design"
  end
end

steve = Rubyist.new("Steve", "Newcomer")

p steve # => #<Rubyist:0x007fe93312ea48 @name="Steve", @experience="Newcomer">

(We cannot forget that embedded in the #p method is an #inspect method, which displays the object and its instance variables.)

From the code above, we see that simply putting super inside the #initialize method automatically passed the parameters “name” and “experience” (quotations for emphasis) up to the #initialize method of the Programmer class.

Generally, we want to think of inheritance as something that occurs at the class level — and that a subclass inherits behavior from its superclass, not state.

This is where super comes in.

— — — —

If we include a parenthesis after super — like such, super() — we will depart from super’s natural behavior of forwarding all the arguments on to the superclass.

# code omitted

class Rubyist < Programmer
  def initialize(name, experience)
    super
  end

# code omitted
end

steve = Rubyist.new("Steve", "Newcomer")

# => 15.rb:2:in `initialize': wrong number of arguments (given 0, expected 2) (ArgumentError)

In this example, the code raises an error. Ruby tells us that the #initialize method in the Programmer class has not been passed two arguments, like it requires.

— — — —

In the below example, we see super with a parenthesis again.

class Programmer
  def initialize(name, experience)
    @name = name
    @experience = experience
  end
end

class Rubyist < Programmer
  
  def initialize(name, experience, best_gem)
    super(name, experience)
    @best_gem = best_gem
  end
end

steve = Rubyist.new("Steve", "Newcomer", "Pry")

Above, we are passing two arguments to super and setting the third argument to the @best_gem instance variable.