Delegating with Forwardable in Ruby

Chris Temple
May 25, 2016 · 2 min read

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!

Chris Temple

Written by

whynot.io

whynot.io

when people say it can’t be done

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade