Delegating with Forwardable in Ruby

Chris Temple
whynot.io
Published in
2 min readMay 25, 2016

Delegation in programming is simply a matter of passing responsibility to an object more suitable to handle a request. It’s good to keep things simple and delegate when you can, that’s why Ruby has made doing so easy by providing the Forwardable module.

Including the Forwardable module in our classes gives us the ability to forward a request/method call to an underlying attribute more appropriate to deal with it.

Forwardable in the wild

Let’s assume that we’re building an app for an agency with agents who have different levels of clearance.

# agent.rb
class Agent
def initialize(name, level)
@name = name
@clearance_level = level
end
def has_clearance?(clearance_level_required)
@clearance_level < clearance_level_required
end
end

If we lived in a simple world, this code would suffice in getting the job done; reality however is that now there are multiple aspects to security at this organisation such as: access rights to files, rooms, secret headquarters etc.

Given all of these extra security features, should the agent be responsible for taking care of them all? Nope! If the agent is anything like me, they would delegate it.

# agent.rb
class Agent
attr_reader :name, :clearance_level
def initialize(name, level)
@name = name
@clearance_level = ClearanceLevel.new(level)
end
end
# clearance_level.rb
class ClearanceLevel
def initialize(agent)
@level = level
end
def is_newb?
@level.zero?
end
def is_secret?
@level > 4
end
def is_top_secret?
@level == 10
end
def has_clearance?(level_required)
@level <= level_required
end
end

Okay, so now the ClearanceLevel class is responsible for deciding on what level of clearance an agent has, however now if we want to do anything useful with our agent the code is going to look something like this:

agent = Agent.new("Phil", 10)
if agent.clearance_level.has_clearance?(4)
puts "Welcome to Sector 4 #{agent.name}."
if agent.clearance_level.is_top_secret?
puts "You've got top secret agent clearance!"
end
end

Having to reference agent.clearance each time to find out information on an agents’ clearance level could get repetitive, it would be a lot nicer if we could just ask clearance questions onagent, for example:

agent = Agent.new("Phil", 10)
if agent.has_clearance?(4)
puts "Welcome to Sector 4 #{agent.name}."
if agent.is_top_secret?
puts "You've got top secret agent clearance!"
end
end

Without Forwardable

Without knowing about the Ruby Forwardable module, to accomplish this we would write wrapper functions in agent.rb

# agent.rb
class Agent
attr_reader :name, :clearance_level
def initialize(name, level)
@name = name
@clearance_level = ClearanceLevel.new(level)
end
def has_clearance?(level)
@clearance_level.has_clearance?(level)
end
def is_top_secret?
@clearance_level.is_top_secret?
end
[...]
end

With Forwardable

But, since we now know that the Forwardable module can forward method calls to an object more responsible, we can use it to delegate the wrapper methods to @clearance_level in theagent like so:

# agent.rb
class Agent
extend Forwardable
attr_reader :name, :clearance_level
def initialize(name, level)
@name = name
@clearance_level = ClearanceLevel.new(level)
end
def_delegators :@clearance_level, :is_newb?, :is_secret?, :is_top_secret?, :has_clearance?
end

So by using the Forwardable module we can now call our clearance methods directly on our agent objects were they’ll be delegated to the @clearance_level, meaning we’ve been able to keep agent.rb simple and clean instead of littering it with wrapper methods. Yay!

I hope you find this helpful.

Let me know ways that you’ve used Forwardable!

--

--