In Object-Oriented Programming, there are two ways for classes to work together.
With inheritance, you create class hierarchies, where a parent class shares methods, constants & instance variable definitions with any class that inherits from it.
In Ruby, every object inherits from the
Object class by default.
That’s why you get access to methods like
With composition a class creates (or is given) objects from another class… then it uses these objects to delegate work to them.
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.
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.
We have two classes:
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
This can be for many reasons:
Now, if you try to do this:
It will fail with a
NoMethodError because there is no
write method on
undefined method `write' for #<Computer:0x000055c2e8f7d310> (NoMethodError)
Computer class doesn't know what you mean by
You create a
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
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?
Let's take a look...
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? 🙂
If you're using Rails, or the ActiveSupport gem by itself, then you've access to the
This is how it works:
class Computer delegate :read, :write, to: :@memory def initialize @memory = Memory.new end end
delegate method takes a
prefix argument, which allows you to append a prefix to the method names.
class Computer delegate :read, :write, prefix: "memory", to: :@memory def initialize @memory = Memory.new end end
Resulting in two methods:
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
require 'delegate' class CoolArray < SimpleDelegator end cool = CoolArray.new() cool << "ruby" cool << "gems" p cool
CoolArray behaves like the object you pass to
Why is this useful?
You can add new methods to this object, without having to change the original class.
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 🙂