The Forwardable module in Ruby — Part I

Mehdi Farsi
Jul 16, 2018 · 4 min read

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

Before to start

I’m thrilled to share with you our latest project: Fun Facts about Ruby — Volume 1

Please feel free to spread the word and share this post! 🙏

Thank you for your time!

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
end
jack = 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
end
jack = 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
end
jack = 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 call jack.main_skill — which is more readable than jack.first — then internally the skills.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
end
todolist = 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] => :@screens
def initialize
@cores = (1..8).to_a
@screens = [1, 2]
end
end
macrosoft = 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.

Voilà !

Feel free to have a look to the Part II about the SingleForwardable module.

May I have your attention please 🎤🎤

Feel free to subscribe here: www.rubycademy.com

Thank you for taking the time to read this post :-)

Feel free to 👏 and share this Medium post if it has been useful for you.

Here is a link to my last medium post: Command-line Arguments in Ruby: Part II

RubyCademy

E-Learning platform for Ruby and Ruby on Rails

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store