The problem with programming can be that there are so many ways to solve a problem. For each solution there are arguments for it and arguments against it.

In recent articles I've written about moving responsibilities into a template object and out of the objects which use them for display.

When the template code first began, its use was extremely simple:

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

By making changes to the template to allow for shared behavior among different types of templates, the way in which our Address class used it became a bit more complex:

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

Originally the Address class knew of two features of the template, that it had a display_address method, and that the method took a single argument intended to be the address.

After some rework, the template became easier to manage and it became easier to make alternative formats, but the changes burdened the user of the object with the need for more knowledge. The Address objects now also need to know that there are setters for street= , apartment= , postal_code= , city= , and province= . It also needs to be implicitly aware that the template could render incomplete data; we know we aren't required to set nil values for certain attributes.

Getting back to simple

We made good changes for the template, but I want that simple interface back. I want my address to act as a value object instead of needing to keep track of passing so many arguments.

While I want to go back to this:

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

I need a way to handle the case where we have sensitive data. What about that protect_privacy? method?

Here's what we could do:

class Address def display(template) if protect_privacy? template.display_address(private_version) else template.display_address(self) end end def private_version self.class.new_with_attributes(city: city, province: province) end end

With this change, the Address can still make a decision about displaying private data and it merely sends that version along to the template. I'm leaving the implementation of new_with_attributes up to imagination, but we'll assume it will set the attributes we've provided on a new instance and return that.

Our template, when last we saw it, looked like this:

class Template attr_accessor :province, :postal_code, :city, :street, :apartment def province_and_postal_code # ... return the combined value or nil end def city_province_postal_code # ... return the combined value or nil end def address_lines [street, apartment, city_province_postal_code].compact end def display_address address_lines.join("

") end end

We've been shifting the method signature of display_address from originally accepting an argument, to then not accepting one, to now requiring one. That's generally a bad thing to change since it causes a cascade of changes for any code that uses the particular method. I'd rather not switch back now, so what I can do is provide a way for the template to get the data it needs.

I'm happy to know how to use Forwardable because I can still keep my template code short and sweet. Here's what we can do. First, lets change hte way we interact with the template:

class Address def display(template) if protect_privacy? template.with_address(private_version) else template.with_address(self) end template.display_address end end

Next, we can alter the template by creating the with_address method:

class Template def with_address(address) @address = address end end

Then, we can alter the line where we use attr_accessor to instead query for information from the address and use it as our value object:

require 'forwardable' class Template extend Forwardable delegate [:province, :postal_code, :city, :street, :apartment] => :@address end

As long as we provide an object which has all of those required features, our Templates will work just fine.

Here's the final result for our Template:

require 'forwardable' class Template extend Forwardable delegate [:province, :postal_code, :city, :street, :apartment] => :@address def with_address(address) @address = address end 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, apartment, city_province_postal_code].compact end def display_address address_lines.join("

") end end

With this change, the Template is still responsibile for only the proper display of data and will handle missing data appropriately. Our Address is responsible for the data itself; it will make decisions about what the data is, and whether or not it should be displayed with a given template.