Method Delegation In Ruby
When creating a new Ruby class, it can be helpful to use composition over
inheritance. Let’s look at an example of an Image
. Our Image
has a
File
, but we don’t want the Image
to extend File
. Why not? We don’t want
Image
to have the full interface of File
; just a small subset of it. This is
a perfect use case for composition and method delegation.
Here’s what our Image
class looks like right now. It contains the File
object we’re interested in.
class Image
def initialize(image_path)
@file = File.open(image_path)
end
end
Next, we would like to expose a few of the File
methods on Image
: #path
,
#read
, and #close
. One way to do that is by writing our own methods that
pass the message along.
require 'forwardable'
class Image
def initialize(image_path)
@file = File.open(image_path)
end
def path
@file.path
end
def read
@file.read
end
def close
@file.close
end
end
This quickly gets repetitive and verbose.
Fortunately, Ruby provides an easy means of delegating methods to other objects.
We’ll extend the Forwardable
module, and then use def_delegator
to hook up
the delegation.
class Image
extend Forwardable
def initialize(image_path)
@file = File.open(image_path)
end
def_delegators :@file, :path, :read, :close
end
We’ve accomplished the same functionality in fewer lines, and made it more readable.
Unfortunately, I find Ruby’s core method delegation unintuitively named and
implemented. It’s confusing to remember the Forwardable
module gives us method
delegation, and that the method is named def_delegators
. I wrote this small
post so I can refer to it later, when I need a refresher on how to do method
delegation in Ruby.