In Ruby 📖’s standard library, we have the
Forwardable module. It provides two methods
def_delegators. In Ruby on Rails 📖’s
ActiveSupport gem, there is
delegate; an analog to the
Forwardable methods but perhaps a more intuitive interface and extended interface.
But before we get into
delegate, we need to know the laws.
Obey the Law (of Demeter)
The Law of Demeter, or principle of least knowledge, is about constructing your code to have the least knowledge necessary.
The idea is to avoid the “nosy neighbor syndrome”; where I ask my neighbor to ask their neighbor to ask their neighbor about some important thing. Who knows the source of that information. You’ve perhaps played the classic Game of Telephone and seen how the original message morphs as it works from originator to final receiver.
Imagine you wanted to know a person’s mayor. Here’s a quick possible method chain that violates the Law of Demeter:
The more you chain methods, the more you’re likely to encounter problems.
A Potpourri of Examples
In this example, I’m assuming we have loaded
ActiveSupport and have access to Ruby on Rails’s
gem 'activesupport' require "active_support/core_ext/module/delegation.rb" class Person attr_accessor :name def address Address.new end delegate :city, :city_mayor, to: :address end class Address def city City.new end delegate :mayor, to: :city, prefix: true end class City def mayor Mayor.new end end class Mayor def name "Ms. Mayor" end end
In the above, I could now use the following to get a person’s mayor:
Excuses, Excuses, Excuses
delegate not violating the Law of Demeter? In part because we’re declaring the delegation for all instances of Person. They are all are assumed to have a city (via their
Put another way, when we instantiate a
Person object, it has a
city method (and
More importantly, what the
delegate “macro” does is helps us more clearly delineate what is relevant to the given object’s responsibilities and interface.
Imagine a case where you were writing a test that used the
Person data structure for processing or reporting. Let’s also say that creating/instantiating the Address, City, and Mayor was expensive.
And all you want to do is test the following “given a person, when I render their information on the page, I see their mayor”. In this case, you could leverage a test double of the
person as follows:
person = double(Person, city_mayor: Mayor.new).
This is an RSpec 📖 test double syntax. The double of a class will enforce that you are setting
For that test, the
person object will use the “mocked” city_mayor for it’s value.
Is It Still a Method Chain?
As I mentioned before,
delegate could be considered conceptual method chain. But by using it, you’re saying it’s an expected and acceptable method chain. There are still possibilities of things going wrong, but the parameters of
delegate provide some help to minimize the fragility.