Unraveling the mystery of the super keyword
Using parent functionality 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 essential information about the super
keyword, including more advanced tips that may surprise you.
All my notes are based on years of hands-on experience at iRonin.IT - a top software development company, where we provide custom software development and IT staff augmentation services for a wide array of technologies.
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 the arguments name and email to the call method from the Parent
class:
Child.new.call('John', 'john@gmail.com')
# => "name: John, email: john@gmail.com"
# => "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', 'john@gmail.com') 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', 'john@gmail.com') do
puts 'Hello world!'
end
# => "child call"
Using super in modules
When it comes to the modules and super
in Ruby, you can create interesting code using the prepend
keyword. Prepend simply takes the module and alters the ancestors’ chain for the class where the module was prepended and puts 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 in 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.
Didn't get enough of Ruby?
Check out our free books about Ruby to level up your skills and become a better software developer.