Exploring the super keyword in Ruby
It seems that the super
keyword in Ruby is a straightforward thing; it simply calls the same method from a parent. However, many developers are still not aware of all the features that the super
keyword provides.
This article is not a long one, but I provide the essential information about the super
keyword, including more advanced tips that may surprise you.
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.
If you would like to learn more about prepend
make sure you read the Extending Ruby classes article.