Managing change using a common interface

In a previous article I showed a way to move display code into a template object to manage missing data. Here's what the basic template code looked like:

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

That's relatively short and easy to read code for displaying the data for an address object.

But the display_address method contains the entire algorithm for displaying the data and stripping away any missing values. When we needed to add a new format type we created an HtmlTemplate and it contained duplicated code for the display_address. That reeks of a future bug where we might need a change to the algorithm and only remember to change one template type.

If we add new attributes to our address, we'd need to change every template so it could handle the new data. Inheritance is an easy solution for managing the way multiple types can handle the change.

And because we specifically allow for data to be missing, we can treat our template object like a partially applied function. Here's what our main template will have...

We'll need methods to set the values to be used for the display data, methods to handle the removal of missing values from the data to be processed, and finally the display method.

To set the data values, we can use attr_accessor:

class Template
      attr_accessor :province, :postal_code, :city, :street

      def province_and_postal_code
        value = [province, postal_code].compact.join(' ')
        if value.empty?
          nil
        else
          value
        end
      end

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

      def address_lines
        [street, city_province_postal_code].compact
      end

      def display_address
        address_lines.join("\n")
      end
    end

With that change, our additional template types can inherit from our beginning Template class and change the behavior relevant to the needs of its format:

class HtmlTemplate < Template 
      def display_address
        address_lines.join("
") end end

Eventually we'll find that the addresses we need to handle might require a secondary bit of information like an apartment number.

class Template
      # Additional attribute
      attr_accessor :apartment

      # Updated collection of information
      def address_lines
        [street, apartment, city_province_postal_code].compact
      end
    end

The way our Address objects interact with these templates would change, of course, but could allow the Address to make decisions about what may be revealed to the outside world:

class Address
      def display(template)
        unless protect_privacy?
          template.street = street
          template.apartment = apartment
          template.postal_code = postal_code
        end
        template.city = city
        template.province = province
        template.display_address
      end
    end

By creating a standard set of template methods, we can treat our objects containing data separately from the objects which display them. What else could we do now that we've made this distiction? For example, what might a template for an IO stream look like?

Get a FREE sample chapter

Get a sample chapter of Clean Ruby and you'll be added to my periodic newsletter of helpful Ruby tips.