Forwarding messages with Tell, Don't Ask

My avid use of Forwardable helps me simplify my code to raise up the important parts. When things still end up too complicated, I can reach for null objects to ease my commands into place.

class Person
      def initialize(address)
        @address = address
      end
      def address
        @address || DefaultAddress.new
      end
    end

As we've seen in this code, any Person object will always have an address, but what I do with that Person or address is what can cause some problems in the future.

Displaying an address can sometimes be a complicated matter.

Asking for too much

In the case of displaying the address for a particular person, we might want certain formatting depending upon the available details.

If we only have a person's city, or just city and state, we'd probably only show that. An easy way is to just check for those attributes on the address:

class Person
      def display_address
        "".tap do |string|
          string << address.street unless address.street.nil?
          string << address.city unless address.city.nil?
          string << address.province unless address.province.nil?
          string << address.postal_code unless address.postal_code.nil?
        end
      end
    end

This is easy, but introduces a problem if the display of the address ever changes. With the above code, we are asking for a lot of information from the address. It would be more flexible to move this into the address itself.

class Person
      def display_address
        address.display
      end
    end

    class Address
      def display
        "".tap do |string|
          string << street unless street.nil?
          string << city unless city.nil?
          string << province unless province.nil?
          string << postal_code unless postal_code.nil?
        end
      end
    end

This simplifies the knowledge we store in the Person class for interacting with an address. Now all we need is to know is that the address has a display method.

As an added benefit, it's far shorter and easier to read.

Being prepared for changes

What happens when we need to output a person's address in both an HTML page and a text-only email? In one case we'll need a simple line break, and in another we'll need HTML formatting such as a <br /> element.

Here's an example:

123 Main Street
Arlington, VA 22222

An updated version of our code which would skip missing data and create our desired output would look like this:

class Address
      def display
        province_and_postal_code = [province, postal_code].compact.join(' ')
        province_and_postal_code = nil if province_and_postal_code.empty?

        city_province_postal_code = [city, province_and_postal_code].compact.join(', ')
        city_province_postal_code = nil if city_province_postal_code.empty?

        [street, city_province_postal_code].compact.join("\n")
      end
    end

With simple text a newline is all we need, but in HTML we'll need to provide a break:

123 Main Street
Arlington, VA 22222

We could add features to our Address class to handle changes like this:

class Address
     def display(line_break)
       # same code as above here ...

       [street, city_province_postal_code].compact.join(line_break)
     end
   end

That seems like a simple change, but in order to use it the Person would need to know what type of line break to provide to the address. This would push the decision for how to display the address far away from the address itself.

Instead, we could keep the information for formatting inside an object whose responsibility is specifically for formatting the information. If we require a new type of format, we would then only need to create an object for that format.

For example, our standard template could handle the data with a newline character.

class Template
      def display_address(address)
        province_and_postal_code = [address.province, address.postal_code].compact.join(' ')
        province_and_postal_code = nil if province_and_postal_code.empty?

        city_province_postal_code = [address.city, province_and_postal_code].compact.join(', ')
        city_province_postal_code = nil if city_province_postal_code.empty?

        [address.street, city_province_postal_code].compact.join("\n")
      end
    end

Then our HTML template could handle the data with proper line breaks for it's format:

class HtmlTemplate
      def display_address(address)
        # same code as above here ...

        [address.street, city_province_postal_code].compact.join("
") end end

We should move the duplicated code to a common location so that each template could share it, but this will do for now.

So how might our Person and Address objects use these templates?

All we'll need to do is change our display_address and display methods to accept a template for the formatting.

class Person
      def display_address(template)
        address.display(template)
      end
    end

    class Address
      def display(template)
        template.display_address(self)
      end
    end

If we add new formats, neither or Person nor our Address class will need to change. There's still more we can do to guard against changes but I'll write more about that next time.