You’re going to learn how to use the delegate method, the Forwardable module & the SimpleDelegator class.
Why do we need delegation?
In Object-Oriented Programming, there are two ways for classes to work together.
They are:
Inheritance
Composition
With inheritance, you create class hierarchies, where a parent class shares methods, constants & instance variable definitions with any class that inherits from it.
For example:
In Ruby, every object inherits from the Object class by default.
That’s why you get access to methods like puts, class & object_id.
With composition a class creates (or is given) objects from another class… then it uses these objects to delegate work to them.
For example:
A computer is made from many parts (objects) & each part knows how to do one thing well.
If you want to render something on screen the computer will tell the graphics card what to show, but not how to do it.
That’s composition!
But what if you’re the computer & you want to give access to the graphics card methods?
You have to build some kind of “bridge” between them.
Let’s see how to do that.
Ruby Method Delegation – Example
We have two classes:
Computer
Memory
Given the following code:
class Computer
def initialize
@memory = Memory.new
end
end
class Memory
def initialize
@data = []
end
def write(data)
@data << data
end
def read(index)
@data[index]
end
end
computer = Computer.new
We want to be able to access the memory only through the Computer.
This can be for many reasons:
You only have 1 memory unit, that means you only want 1 memory object. By centralizing access in one object you can achieve that.
You want to control who can access the memory (different applications) & what parts of memory they can use.
You want to log every memory access for security or debugging purposes.
Now, if you try to do this:
computer.write("rubyguides")
It will fail with a NoMethodError because there is no write method on Computer.
undefined method `write' for #<Computer:0x000055c2e8f7d310> (NoMethodError)
The Computer class doesn't know what you mean by write.
Unless...
You create a write method!
Here's how:
class Computer
def initialize
@memory = Memory.new
end
def write(data)
@memory.write(data)
end
def read(index)
@memory.read(index)
end
end
We are passing along the requests to the @memory object.
That's method delegation.
Note: This makes these methods part of your public interface (they become available to everyone) & you don't always want that.
Is there some kind of shortcut for method delegation?
Yes!
Let's take a look...
How to Use The Forwardable Module
Now:
You can use the Forwardable module included with Ruby to define delegator methods.
It works like this:
require 'forwardable'
class Computer
extend Forwardable
def_delegators :@memory, :read, :write
def initialize
@memory = Memory.new
end
end
This allows you to remove the methods we created earlier & it will do the same thing.
Forwardable will take care of method arguments (including blocks!) for you, so you don't have to worry about that.
Isn't that nice? 🙂
How to Use The Rails Delegate Method
If you're using Rails, or the ActiveSupport gem by itself, then you've access to the delegate method.
This is how it works:
class Computer
delegate :read, :write, to: :@memory
def initialize
@memory = Memory.new
end
end
The delegate method takes a prefix argument, which allows you to append a prefix to the method names.
Example:
class Computer
delegate :read, :write, prefix: "memory", to: :@memory
def initialize
@memory = Memory.new
end
end
Resulting in two methods:
memory_read
memory_write
How to Delegate Everything With SimpleDelegator
These techniques you just learned are used to forward or delegate specific methods.
But if you want to wrap an object & expose all of its methods...
You can use the SimpleDelegator class!
Here's how:
require 'delegate'
class CoolArray < SimpleDelegator
end
cool = CoolArray.new([])
cool << "ruby"
cool << "gems"
p cool
Now CoolArray behaves like the object you pass to new.
Why is this useful?
You can add new methods to this object, without having to change the original class.
Video Tutorial
Summary
You've learned about object composition in Ruby & how to delegate methods using different techniques!
If you enjoyed this article please share it with your Ruby friends.
Thanks for reading 🙂
Related
4 comments
Daniel Long says
4 years ago
Under the heading “How to Use The Forwardable Module” it says def_delegator in the first image. This doesn’t work. In the video he uses def_delegators and that, of course, works perfectly… Just a typo.