I thought I’d share a git commit from today which I found quite interesting. It is the adding of a to_str method to an Address value object. In the spec I type check the return on to_str to make sure it is a String.

Add Address#to_str since address can easily be coerced in to a String

First the spec:

require 'spec_helper'
require 'active_model'
require 'virtus'
require_relative '../../app/models/address'

describe Address do
  describe '#to_s' do
    it 'includes all lines comma delimited' do
      address = Address.new(:line_1 => 'a', :line_2 => 'b', :line_3 => 'c', :postcode => 'd')
      address.to_s.should == 'a, b, c, d'
    end

    it 'excludes blank lines' do
      address = Address.new(:line_1 => 'a', :line_3 => 'c', :postcode => 'd')
      address.to_s.should == 'a, c, d'
    end
  end

  describe '#to_str' do
    it 'returns a string' do
      address = Address.new(:line_1 => 'a', :postcode => 'b')
      address.to_str.should be_instance_of(String)
    end
  end
end

An object should only implementation to_str if it provides a superset of the String API, in other words it can duck type to a String and be used in place of a string.

Therefore in my spec the safest thing to do is to a type check to make sure the reutrned object is a String.

The implementation:

class Address
  include Virtus::ValueObject
  include ActiveModel::Validations

  attribute :line_1, String
  attribute :line_2, String
  attribute :line_3, String
  attribute :line_4, String
  attribute :postcode, String

  validates :line_1,   :presence => true
  validates :line_2,   :presence => true
  validates :postcode, :presence => true

  def to_s
    [line_1, line_2, line_3, line_4, postcode].reject(&:blank?).join(', ')
  end

  alias_method :to_str, :to_s
end