The Forwardable module in Ruby — Part I
In this article we’re going to explore the following topics:
- the
Forwardable
module - the
def_delegator
method - the
def_delegators
method - the
delegate
method
The Forwardable module
Forwardable
is a module that can be used to add behavior to all the instances of a given class.
This module is included to the singleton class using the extend
keyword in order to add methods at class-level (to keep it simple).
So let’s break down the API.
The def_delegator method
The Forwardable#def_delegator
method allows an object to forward a message to a defined receiver.
NB: feel free to have a look to my article if you’re unfamiliar with the notion of
messages and receivers
in Ruby.
Nothing better than example to demystify the previous assertion
# in forwardable.rb
class Hero
attr :skills def initialize
@skills = [:strong, :keen, :brave]
end
endjack = Hero.newputs "Jack's main skill: #{jack.skills.first}"
produces
?> ruby forwardable.rb
Jack's main skill: strong
This works.. But calling jack.skills.first
outside of the Hero
class definition is a bit.. weirdo !
So let’s encapsulate this code into the Hero
class definition
# in forwardable.rb
class Hero
attr :skills def initialize
@skills = [:strong, :keen, :brave]
end def main_skill
@skills.first
end
endjack = Hero.newputs "Jack's main skill: #{jack.main_skill}"
produces
?> ruby forwardable.rb
Jack's main skill: strong
Better ! here the Hero#main_skill
method contains the logic to access to the hero main skill.
This solution is acceptable. But Ruby provides a mechanism to forward a message (#first
) from an instance (jack
) to an explicit receiver (skills
) using the Forwardable#def_delegator
method.
So let’s modify our gist using this method
# in forwardable.rb
require 'forwardable'class Hero
attr :skills extend Forwardable def_delegator :@skills, :first, :main_skill def initialize
@skills = [:strong, :keen, :brave]
end
endjack = Hero.newputs "Jack's main skill: #{jack.main_skill}"
produces
?> ruby forwardable.rb
Jack's main skill: strong
Cool ! Here we avoid to create a getter method to access skills.first
by using the message forwarding system provided by Ruby.
First, we require the forwardable
library.
Then, we extend Forwardable
in order to add the module methods at class-level.
Then we use the freshly added class-level method def_delegator
:
- The first argument
:@skills
correspond to the receiver of the message forwarding. - The second argument
:first
is the message to forward. - And finally the third argument
:main_skill
is an alias of the:first
message. So, when we calljack.main_skill
— which is more readable thanjack.first
— then internally theskills.first
will be automatically called.
The def_delegators
method
The 2 main differences with the def_delegator
method is that it takes a set of methods to forward and the methods cannot be aliased
# in forwardable.rb
require 'forwardable'class Todolist
attr :tasks extend Forwardable def_delegators :@tasks, :first, :last def initialize
@tasks = %w[conception implementation refactoring]
end
endtodolist = Todolist.newputs "first tasks: #{todolist.first}"
puts "last tasks: #{todolist.last}"
produces
?> ruby forwardable.rb
first tasks: conception
last tasks: refactoring
Here, the first
and last
method of the tasks
array are available for any instance of Todolist
.
when one of these 2 methods are called then the message is forwarded to the tasks
array.
The delegate
method
The delegate
method accepts a hash as argument where:
- the key is one or more messages
- the value is the receiver of the messages defined as key
# in forwardable.rb
require 'forwardable'class Computer
attr :cores, :screens extend Forwardable delegate %I[size] => :@cores,
%I[length] => :@screensdef initialize
@cores = (1..8).to_a
@screens = [1, 2]
end
endmacrosoft = Computer.newputs "Cores: #{macrosoft.size}"
puts "Screens: #{macrosoft.length}"
produces
$> ruby forwardable.rb
Cores: 8
Screens: 2
Here, the macrosoft.size
message corresponds to the macrosoft.cores.size
.
And, the macrosoft.length
message corresponds to the macrosoft.screens.length
.
Feel free to have a look to the
Part II
about theSingleForwardable
module.
Voilà !
RubyCademy is now available on Youtube! ▶️ 🚀 🤩 💎
We publish short videos (maximum 5 minutes) that talk about technical notions, quick wins and tools (..and a couple of geek stuffs 😅).
Feel free to click on the image below to access our Youtube channel