Super keyword in Ruby

Brackets versus non-brackets version

I usually ask the following question during the technical interview for the Ruby developers: what is the difference between calling super and super()?

Passing all arguments automatically to the parent

When you call super, you pass all the arguments to the parent implicitly. To better visualize this, let’s consider the following example:

class Parent
  def call(name, email)
    puts "name: #{name}, email: #{email}"
  end
end

class Child < Parent
  def call(name, email)
    super
    puts "child call"
  end
end

By invoking super in the Child#call method, we implicitly pass arguments name and email to the call method from the Parent class:

Child.new.call('John', '[email protected]')
# => "name: John, email: [email protected]"
# => "child call"

Passing the arguments to the parent explicitly

If in the above example, we would call super() instead of super, we would receive the following error:

ArgumentError: wrong number of arguments (given 0, expected 2)

It happens because calling super() won’t pass any arguments to the parent method (and because the parent method accepts two arguments, we get an ArgumentError error).

Calling the parent’s block

It is also possible to call a block provided by the parent class:

class Parent
  def call
    yield if block_given?
  end
end

class Child < Parent
  def call(name, email)
    super()
    puts "child call"
  end
end

Child.new.call('John', '[email protected]') do
  puts 'Hello world!'
end
# => "Hello world!"
# => "child call"

In the above example, we didn’t allow to pass any arguments to the parent but invoking the block was still possible. We can block this behavior as well:

class Child < Parent
  def call(name, email)
    super(&nil)
    puts "child call"
  end
end

Child.new.call('John', '[email protected]') do
  puts 'Hello world!'
end
# => "child call"

Using super in modules

When it comes to the modules and super in Ruby, you can create an interesting code using the prepend keyword. Prepend simply takes the module and alter the ancestors’ chain for the class where the module was prepended and put the module in the first place:

module SomeModule; end

class SomeClass
  prepend SomeModule
end

SomeClass.ancestors
# => [SomeModule, SomeClass, Object, Kernel, BasicObject]

As you can see on the above snippet, the SomeModule module was put before the SomeClass class. If you would define the same method in the module and the class, calling super from the module will call the method from SomeClass.

It’s easier to explain it by writing some code. We can create a simple benchmark class that will measure the execution time of a given method:

module ExecutionTimer
  def call
    time = Time.now
    super
  ensure
    result = Time.now - time
    puts "Call executed in #{result} seconds"
  end
end

class Service
  prepend ExecutionTimer

  def call
    sleep(2)
  end
end

I used ensure in the call method from the module to be sure that the time will be calculated even if the parent method call would raise an error.

Let’s give it a try:

Service.new.call
# => Call executed in 2.000332 seconds

With the prepend and super keywords, you can create helpful “wrappers” for your classes to extend a given method’s functionality.