Blog

Ruby Forwardable deep dive

by Jim

The Forwardable library is one of my favorite tools from Ruby’s standard library both for simplifying my own code and learning how to make simple libraries.

I find that the best way to understand how code works is to first understand why it exists and how you use it. In a previous article I wrote about the value of using Forwardable. It takes code like this:

def street
  address.street
end

def city
  address.city
end

def state
  address.state
end

And makes it as short as this:

delegate [:street, :city, :state] => :address

Shrinking our code without losing behavior is a great feature which Forwardable provides. So how does it work?

Modules and their context

Forwardable is a module, which can be used to add behavior to an object. Most of the of modules I see tend to be used like this:

class Person
  include SuperSpecial
end

But Forwardable is different and is designed to be used with the extend method.

require 'forwardable'
class Person
  extend Forwardable
end

Using extend includes the module into the singleton_class of the current object. There’s a bit more to it than that, but here’s a simple model to keep in mind: use include in your class to add instance methods; use extend in your class to add class methods.

Now that we have that out of the way, to use Forwardable, use extend.

Defining forwarding rules

My most often used feature of Forwardable is the one you saw above: delegate. It accepts a hash where the keys can be symbol or string method names, or an array of symbols and strings. The values provided are accessors for the object to which you’ll be forwarding the method names.

class Person
  extend Forwardable

  delegate [:message_to_forward, :another_method_name] => :object_to_receive_message,
            :single_method => :other_object
end

Other shortcuts

Forwardable provides a few methods, and most commonly you’ll see their shortened versions: delegate, def_delegator, and def_delegators. These are actually alias methods of the originals.

alias delegate instance_delegate
alias def_delegators def_instance_delegators
alias def_delegator def_instance_delegator

The delegate method we reviewed above is a bit of a shortcut for similar behavior that other methods provide in Forwardable.

The def_delegators method accepts multiple arguments but it’s sometimes hard for me to remember that one argument in particular is important. The first argument is the reference to the related object, the next arguments are used to create methods to forward.

class SpecialCollection
  extend Forwardable

  def_delegators :@collection, :clear, :first, :push, :shift, :size
  # The above is equivalent to:
  delegate [:clear, :first, :push, :shift, :size] => :@collection
end

As you can see, with delegate there’s a visual separation between the accessor and the list of methods.

There’s more of a difference between delegate and def_delegators too.

def instance_delegate(hash) # aliased as delegate
  hash.each{ |methods, accessor|
    methods = [methods] unless methods.respond_to?(:each)
    methods.each{ |method|
      def_instance_delegator(accessor, method)
    }
  }
end

Here the code loops through the hash argument changing the keys into arrays of methods if they aren’t already arrays, and then calls the def_instance_delegator method for each item in the array. Here’s what def_instance_delegators looks like. Note that this is the plural version:

def def_instance_delegators(accessor, *methods) # aliased as def_delegators
  methods.delete("__send__")
  methods.delete("__id__")
  for method in methods
    def_instance_delegator(accessor, method)
  end
end

This method speficially restricts the use of __send__ and __id__ in forwarded messages. These methods are particularly important in communicating with the forwarding object and determining its identity. If you only used delegate and (for some strange reason) you specify either of __send__ or __id__ then those methods will pass right through. That might do exactly what you want or it might introduce some buggy behavior. This is mostly easy to avoid since you’ll likely specify all the methods you need.

The different behavior is important to know, however, if you want to do a blanket forward for all methods from another class of objects:

class SpecialCollection
  extend Forwardable

  def_delegators :@collection, *Array.instance_methods
  # The above is equivalent to:
  delegate [*Array.instance_methods] => :@collection
end

If you do that, you’ll likely see warnings from Ruby like this:

warning: redefining `__send__' may cause serious problems

Don’t say Ruby didn’t warn you!

But def_delegators is a plural version of def_delegator which provides more options than the two we’ve been reviewing.

class SpecialCollection
  extend Forwardable

  def_delegator :@collection, :clear, :remove
  def_delegator :@collection, :first
end

The method def_delegator accepts only three arguments. The first is the accessor for the related object (which will receive the forwarded message) and the second is the name of the message to be sent to the related object. The third argument is the name of the method to be created on the current class and is optional; if you don’t specify it then the second argument will be used.

Here’s what the above def_delegator configurations would look like if you wrote out the feature yourself:

class SpecialCollection
  extend Forwardable

  # def_delegator :@collection, :clear, :remove
  def remove
    @collection.clear
  end

  # def_delegator :@collection, :first
  def first
    @collection.first
  end
end

You can see how the optional third argument is used as the name of the method on your class (e.g. remove instead of clear).

How the methods are created

We looked at how Forwardable adds class methods to your class. Let’s look at the most important one:

def def_instance_delegator(accessor, method, ali = method)
  line_no = __LINE__; str = %{
    def #{ali}(*args, &block)
      begin
        #{accessor}.__send__(:#{method}, *args, &block)
      rescue Exception
        $@.delete_if{|s| Forwardable::FILE_REGEXP =~ s} unless Forwardable::debug
        ::Kernel::raise
      end
    end
  }
  # If it's not a class or module, it's an instance
  begin
    module_eval(str, __FILE__, line_no)
  rescue
    instance_eval(str, __FILE__, line_no)
  end
end

It looks like a lot, and it is, but let’s strip it down to it’s simplest form rather than review everything at once. Here’s a simpler version:

def def_instance_delegator(accessor, method, ali = method)
  str = %{
    def #{ali}(*args, &block)
      #{accessor}.__send__(:#{method}, *args, &block)
    end
  }
  module_eval(str, __FILE__, __LINE__)
end

Remembering, of course, that def_instance_delegator is aliased as def_delegator we can see that a string is created which represents what the method definition will be and saved to the str variable. Then then that variable is passed into module_eval.

It’s good to know that module_eval is the same as class_eval because I know I often see class_eval but rarely see the other. Regardless, class_eval is merely an alias for module_eval.

The string for the generated method is used by module_eval to create the actual instance method. It evaluates the string and turns it into Ruby code.

Taking this command def_delegator :@collection, :clear, :remove here’s what string will be generated:

%{
  def remove(*args, &block)
    @collection.__send__(:clear, *args, &block)
  end
}

Now it’s a bit clearer what’s going to be created.

If you’re not familiar with __send__, know that it’s also aliased as send. If you need to use the send method to match your domain language, you can use it and rely on __send__ for the original behavior. Here, the Forwardable code is cautiously avoiding any clashes with your domain language just in case you do use “send” as a behavior for some object in your system.

Maybe you’re scratching your head about what either of those methods are at all. What the heck is send anyway!?

The simplest way to describe it is to show it. This @collection.__send__(:clear, *args, &block) is equivalent to:

@collection.clear(*args, &block)

All Ruby objects accept messages via the __send__ method. It just so happens that you can use the dot notation to send messages too. For any method in your object, you could pass it’s name as a string or symbol to __send__ and it would work the same.

It’s important to note that using __send__ or send will run private methods as well. If the clear method on @collection is marked as private, the use of __send__ will circumvent that.

The methods defined by Forwardable will accept any arguments as specified by *args. And each method may optionally accept a block as referred to in &block.

It’s likely that the acceptance of any arguments and block will not affect your use of the forwarding method, but it’s good to know. If you send more arguments than the receiving method accepts, your forwarding method will happily pass them along and your receiving method will raise an ArgumentError.

Managing errors

Forwardable maintains a regular expression that it uses to strip out references to itself in error messages.

FILE_REGEXP = %r"#{Regexp.quote(__FILE__)}"

This creates a regular expression where the current file path as specified by __FILE__ is escaped for characters which might interfere with a regular expression.

That seems a bit useless by itself, but remembering the original implementation of def_instance_delegator we’ll see how it’s used:

str = %{
  def #{ali}(*args, &block)
    begin
      #{accessor}.__send__(:#{method}, *args, &block)
    rescue Exception
      $@.delete_if{|s| Forwardable::FILE_REGEXP =~ s} unless Forwardable::debug
      ::Kernel::raise
    end
  end
}

This code recues any exceptions from the forwarded message and removes references to the Forwardable file.

The $@ or “dollar-at” global variable in Ruby refers to the backtrace for the last exception raised. A backtrace is an array of filenames plus their relevant line numbers and other reference information. Forwardable defines these forwarding methods to remove any lines which mention Forwardable itself. When your receive an error, you’ll want the error to point to your code, and not the code from the library which generated it.

Looking at this implementation we can also see a reference to Forwardable::debug which when set to a truthy value will not remove the Forwardable lines from the backtrace. Just use Forwardable.debug = true if you run into trouble and want to see the full errors. I’ve never needed that myself, but at least it’s there.

The next thing to do, of course, is to re-raise the cleaned up backtrace. Again Forwardable will be careful to avoid any overrides you may have defined for a method named raise and explicitly uses ::Kernel::raise.

The double colon preceding Kernel tells the Ruby interpreter to search from the top-level namespace for Kernel. That means that if, for some crazy reason, you’ve defined a Kernel underneath some other module name (such as MyApp::Kernel) then Forwardable will use the standard behavior for raise as defined in Ruby’s Kernel and not yours. That makes for predictable behavior.

Applying the generated methods

After creating the strings for the forwarding methods, Forwardable will attempt to use module_eval to define the methods.

# If it's not a class or module, it's an instance
begin
  module_eval(str, __FILE__, line_no)
rescue
  instance_eval(str, __FILE__, line_no)
end

If the use of module_eval raises an error, then it will fallback to instance_eval.

I’ve yet to find a place where I’ve needed this instance eval feature, but it’s good to know about. What this means is that not only can you extend a class or module with Forwardable, but you can extend an individual object with it too.

object = Object.new
object.extend(Forwardable)
object.def_delegator ...

This code works, depending of course on what you put in your def_delegator call.

Forwarding at the class or module level

All these shortcuts for defining methods are great, but they only work for instances of objects.

Fortunately forwardable.rb also provides SingleForwardable, specifically designed for use with modules (classes are modules too).

class Person
  extend Forwardable
  extend SingleForwardable
end

In the above sample you can see that Person is extended with both Forwardable and SingleForwardable. This means that this class can use shortcuts for forwarding methods for both instances and the class itself.

The reason this library defines those longform methods like def_instance_delegator instead of just def_delegator is for a scenario like this. If you wanted to use def_delegator and those methods were not aliased, you’d need to choose only one part of this library.

class Person
  extend Forwardable
  extend SingleForwardable

  single_delegate [:store_exception] => :ExceptionTracker
  instance_delegate [:street, :city, :state] => :address
end

As you can probably guess from the above code, the names of each library’s methods matter.

alias delegate single_delegate
alias def_delegators def_single_delegators
alias def_delegator def_single_delegator

If you use both Forwardable and SingleForwardable, you’ll want to avoid the shortened versions like delegate and be more specific by using instance_delegate for Forwardable, or single_delegate for SingleForwardable.

If you liked this article, please join my mailing list at http://clean-ruby.com or pick up the book today!

Clean Up Your Code

If you liked this post and want more like it, get periodic tips in your inbox by leaving your email address below.

To get more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.



Permalink…

Avoiding clever mistakes when displaying data with missing values

by Jim

In a previous article I showed a snippet of code I’ve used for displaying address information. There were some tricks to getting it right that are valuable to know when you have to handle missing data.

Here’s the problem, and how to solve it.

Let’s setup some simple data to use:

street = "123 Main St."
city = "Arlington"
province = "VA"
postal_code = "222222"

The original implementation of displaying an address looked like this:

"".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

While that code will skip missing data if there is any, it will just mash all the bits together creating this:

"123 Main St.ArlingtonVA22222"

That’s not particularly helpful for displaying a readable address. I’m looking for it to display like this:

"123 Main St.
Arlington, VA 22222"

Here’s what I tried next:

[street, [city, [province, postal_code].join(' ')].join(', ')].join("\n")

Thinking I was very clever, I ran the code with complete data and it worked quite well.

When I ran it with incomplete data I found that it didn’t work the way I expected. For example, if the city value was nil, I got this:

"123 Main St.
, VA 22222"

That leading comma is just visual noise, so we need to remove that. Realizing that I had nil values in my arrays, I knew I could reach for the compact method to strip them out. After compacting the array, the join wouldn’t have nil values to address; they’d be gone.

[street, [city, [province, postal_code].compact.join(' ')].compact.join(', ')].compact.join("\n")

This worked perfectly, to remove the leading comma:

"123 Main St.
VA 22222"

Just to be sure I got it right, I began checking other data. Next, with the city set to “Arlington” and this time with the province and postal_code set to nil I saw this:

"123 Main St.
Arlington, "

Ugh! Now I had a trailing comma. Why wasn’t this working!? Using compact should remove the nil values.

The problem was that I had an empty array for the province and postal_code. That meant that with both values removed from the array using compact, this was happening:

[].join(' ') #=> ""

And because that empty array returned a value of an empty string, I was joining the city value with an empty string. In this case, compact was doing nothing for me.

[city, [province, postal_code].compact.join(' ')].compact.join(', ')
# is the same as:

[city, ""].compact.join(', ')]
# which yields:

"Arlington, "

So there it was. Finally I found my problem that what I thought was nil, wasn’t.

I decided to change the values to nil if they were empty strings:

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")

Finally, I got the output I needed:

"123 Main St.
Arlington"

"123 Main St.
VA 22222"

"123 Main St.
Arlington, VA 22222"

My clever one-liner gave me unexpected behavior. While it was nice and short, it was also wrong. Eventually I ended up with code which is not just correct, but much more readable too. You might have your own preferences for how to handle these nil values. What would you do differently?

Clean Up Your Code

If you liked this post and want more like it, get periodic tips in your inbox by leaving your email address below.

To get more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.



Permalink…

Enforcing encapsulation with East-oriented Code

by Jim

Often our programs become complicated inadventently. We don’t intend to put things it the wrong place, it just seems to happen.

Most of the time it happens to me when I allow my objects to leak information and eventually their responsibilities.

In recent articles I showed code that handled displaying address details and how to separate the responsibility for formatting from the responsibility for data. But there’s still a problem which would allow me or someone else to unintentionally leak responsibility from these objects.

It’s a good idea to be guarded against how much you reveal from an object; you never know how someone might use it in the future.

In our Template code, we provide a number of values about the object: province, postalcode, city, street, apartment, provinceandpostalcode, cityprovincepostalcode, addresslines, and display_address. With each attribute provided, we introduce the ability to other objects to query the information and make decsions based upon the answer.

It’s far too easy to write these types of queries:

if template.province == "..."
if template.city == "..." && template.postal_code == "..."
if template.city_province_postal_code.include?("...")

But what would our code look like if we couldn’t do this? What if there were no questions to ask?

What if the only accessible information from our template was the display_address used to show the formatted data?

require 'forwardable'
class Template
  extend Forwardable

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

  def with_address(address)
    @address = address
  end

  private
  delegate [:province, :postal_code, :city, :street, :apartment] => :@address

  def province_and_postal_code
    # ...
  end

  def city_province_postal_code
    # ...
  end

  def address_lines
    [street, apartment, city_province_postal_code].compact
  end
end

By moving most of our methods under the private keyword, our Template interface has shrunk significantly. Now all we’ll have to handle and all other objects will need to know about is the display_address and with_address methods.

East vs. West

The changes we made make a significant restriction on the questions that we can ask about an object. This is where the idea of East-orientation comes in.

If we imagine a compass applied to our source code we’d see that any query, any if, will send the information flowing westward.

# <---- information travels West
if template.city == "..."

The if handles the execution of the algorithm. But by removing methods from the public interface which provide attributes like above, we better encapsulate the data in the target object. Our template here could not answer a question about its city attribute.

Instead, the code which uses the template would be forced to command the template to perform a particular action. The body of the if could instead become a method on the template object.

# ----> information travels East
template.perform_action

The template can make it’s own decisions about what to do when told to perform some action.

Enforce encapsulation with return values

An easy way to ensure that our code encourages commands, discourages queries, and enforces encapsulation is to control the return values of our methods.

The best thing to return is not necessarily the result of the method, but the object performing the method. It’s as simple as adding self to the end of the method block.

Here’s what that might look like:

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

By adding self there, each time we set the address value object using with_address we are given the object itself back, instead of the value that we passed to it.

# Without appending "self"
template.with_address(address) #=> address

# After appending "self"
template.with_address(address) #=> template

This becomes a powerful change to the way we interact with the template object. It enforces the encapsulation of data of the template and it forces us to think more about sending messages to our objects and allowing them to implement the solution.

When we return the object itself, we can only continue operation on that object.

The added benefit is that our code will become more concise. We will prevent unintentional dependencies between objects. And we can chain our commands together; it’s all the same object:

template.with_address(address).display_address

See the flow at a glance

By using a visual compass to guide us through our code, it’s easy to step back and see exactly where we leave our objects leaking information and responsibility.

Each time we query an object, each time we set a variable, we should now see the westward flow of information.

By simply returning self from our methods, we will force the hand of every developer to think with East-orientation in mind. By only working with the same object we will get back to using objects in the way that tends to be the most useful: for handling messages and implementing the required algorithm.

One last issue is that of the display_address method. Currently it returns the string representation of the address and not the template itself.

We can change that. What you do depends on how you’re using a template. Perhaps our base Template will output details to STDOUT, or perhaps to a text file. Here’s how we’d take care of that:

class Template
  def display_address
    STDOUT.puts address_lines.join("\n")
    # or perhaps File.write ...
    self
  end
end

Try this with your code. Return “self” and see how it changes your thinking. Scan for westward flow of information and see how you can push responsibilities into the appropriate objects by heading East.

From now until the end of the year, you can get Clean Ruby for only $42 (the original pre-release price) by using this link. It'll only be valid this year (2014) and will go up automatically in January 2015. Merry Christmas!

Clean Up Your Code

If you liked this post and want more like it, get periodic tips in your inbox by leaving your email address below.

To get more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.



Permalink…

Preferring value objects or setters and arguments

by Jim

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("\n")
  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("\n")
  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.

Clean Up Your Code

If you liked this post and want more like it, get periodic tips in your inbox by leaving your email address below.

To get more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.



Permalink…

Managing change using a common interface

by Jim

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("<br />")
  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?

Clean Up Your Code

If you liked this post and want more like it, get periodic tips in your inbox by leaving your email address below.

To get more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.



Permalink…

Forwarding messages with Tell, Don't Ask

by Jim

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<br />
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("<br />")
  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.

Clean Up Your Code

If you liked this post and want more like it, get periodic tips in your inbox by leaving your email address below.

To get more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.



Permalink…

1999 - 2015 © Saturn Flyer LLC 2321 S. Buchanan St. Arlington, VA 22206

Call Jim Gay at 571 403 0338