Graffiti arrows

Annie Spratt

Delegate to simplify your code

One way of thinking about object-oriented programming is as passing messages between objects.

In many cases you may want to surface a public method on a associated object as if it was a method on the original object.

The two main ways to do this are:

  • the Forwardable functionality in the the Ruby Standard Library, documentation here.
  • the delegate method, an Active Support core extension (available if you’re using Rails) documentation here.

Instead of…

… creating new methods to call directly to associated objects.

# Plain Ruby
class Workspace
  attr_reader :user

  def initialize(user)
    @user = user
  end

  def user_email
    @user.email
  end
end

# Inside Rails
class Workspace < ApplicationRecord
  belongs_to :user

  def user_email
    user.email
  end
end

Use…

…the Forwardable functionality or delegate method..

# Plain Ruby
class Workspace
  extend Forwardable

  attr_reader :user

  def initialize(user)
    @user = user
  end

  def_delegator :@user, :email, :user_email
end

# Inside Rails
class Workspace < ApplicationRecord
  belongs_to :user

  delegate :email, to: :user, allow_nil: true, prefix: true
  # allow_nil swallows errors if user is nil
  # prefix makes the name of the method user_email
end

But why?

If you are ‘passing through’ messages in Ruby or Rails either style is preferable to creating a new method. It’s a clearer expression of what you’re trying to achieve in your code. The different look of the code helpfully creates a visual distiction between ‘just calling methods on an associated object’ and where you’re actually implementing new functionality.

As is typical, the Rails version is syntactically neater and provides greater flexibility and functionality. I particularly like the prefix and allow_nil options. If you’re using Rails you may as well use the enhancements that Rails provides for delgation.

Why not?

When using the method there is no clarity improvement in workspace.user_email over workspace.user.email. However if you’re changing the method name, for example workspace.owner_email, to better show your intent, there may be a benefit.

There are other more involved delegation techniques available using Ruby’s Delegator library (documentation here) but those are more useful when wrapping entire classes in additional functionality, rather than passing a handful of methods.

Last updated on August 5th, 2018