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:
Forwardablefunctionality in the the Ruby Standard Library, documentation here.
delegatemethod, an Active Support core extension (available if you’re using Rails) documentation here.
… 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
Forwardable functionality or
# 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
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
allow_nil options. If you’re using Rails you may as well use the enhancements that Rails provides for delgation.
When using the method there is no clarity improvement in
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.
photo by Annie Spratt
Don’t miss my next post, sign up to the One Ruby Thing email and get my next post in your inbox.