Thoughts of an Eaten Sun

through the mind of kyle tolle

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.