This article is about method delegation in Ruby.
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
[responsive_video type='youtube' hide_related='0' hide_logo='0' hide_controls='0' hide_title='1' hide_fullscreen='0' autoplay='0']https://www.youtube.com/watch?v=qbAcpubjLJU[/responsive_video]
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 🙂
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.
Hey Daniel,
you’re correct! Thanks for pointing this out, just fixed the typo 🙂
Thank you, useful info 👍🏻
Thanks for reading! 🙂