giant robots smashing into other giant robots

We are thoughtbot. We make web & mobile apps.

Tagged:

Comments (View)

fundamentals: TDD a date helper

I want to format a date range in a view for our training app. This is a job for helpers. A fundamental skill for Rails developers is writing the helper using TDD. This process takes about 10 minutes and results in a confidence-building regression suite.

Working outside-in, we start in the interface:

<%= format_date_range(course.date_range) %>

Tests fail, “format_date_range” doesn’t exist.

script/generate helper date_time

Produces:

# app/helpers/date_time_helper.rb
module DateTimeHelper
end

# test/unit/helpers/date_time_helper_test.rb
require 'test_helper'

class DateTimeHelperTest < ActionView::TestCase
end

Incidentally, former thoughtbot intern Eugene Bolshakov wrote the Rails patch that creates the helper test stub generator.

First case:

should "format date range on same day" do
  eight_oclock = DateTime.new(2009, 10, 12, 8)
  nine_oclock  = DateTime.new(2009, 10, 12, 9)
  date_range   = eight_oclock..nine_oclock
  expected     = "October 12, 2009"

  assert_equal expected, format_date_range(date_range)
end

My style is flat test structure, intention-revealing temporary variables, respect an 80-character line limit.

Make it pass:

def format_date_range(date_range)
  first = date_range.first
  last  = date_range.last
  if same_day?(first, last)
    "#{first.to_s(:month_day)}, #{first.year}"
  end
end

private

def same_day?(date_one, date_two)
  date_one.day == date_two.day
end

More temporary variables. Simple private method is there for a more expressive question in the conditional.

Next case:

should "format date range on different days of same month" do
  monday     = DateTime.new(2009, 10, 12)
  tuesday    = DateTime.new(2009, 10, 13)
  date_range = monday..tuesday
  expected   = "October 12-13, 2009"

  assert_equal expected, format_date_range(date_range)
end

Same pattern. Make it pass:

def format_date_range(date_range)
  # ...
  elsif same_month?(first, last)
    "#{first.to_s(:month_day)}-#{last.day}, #{last.year}"
  end
end

private

def same_month?(date_one, date_two)
  date_one.month == date_two.month
end

Same pattern. Date and time formatting already has a home in config/initializers/time_formats.rb, so we’re using our existing date format, month_day. Here’s what my typical initializer looks like:

{ :short_date  => "%x",                 # 04/13/10
  :long_date   => "%a, %b %d, %Y",      # Tue, Apr 13, 2010
  :longer_date => "%B %d, %Y %H:%M %Z", # April 13, 2010 11:20
  :index       => "%Y/%m/%d %H:%M",     # 2010/04/13 11:20
  :standard    => "%B %d, %Y",          # April 13, 2010
  :month_day   => "%B %d",              # April 13
  :abbr_month  => "%b"                  # Apr
}.each do |key, value|
  ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.update(
    key => value
  )
end

Final case:

should "format date range on days of different months" do
  october    = DateTime.new(2009, 10, 31)
  november   = DateTime.new(2009, 11, 1)
  date_range = october..november
  expected   = "October 31-November 01, 2009"

  assert_equal expected, format_date_range(date_range)
end

Make it pass:

else
  "#{first.to_s(:month_day)}-#{last.to_s(:month_day)}, #{last.year}"
end

There’s room for improvement: that leading zero sucks. We could use %e instead of %d, which replaces the leading zero with a space:

expected   = "October 31-November  1, 2009"

That makes the test look a little ridiculous, but since the output will be HTML, the extra space is fine.

Make it pass:

:month_day   => "%B %e"

Complete source code.

Sneak peak of the app in progress, taking advantage of the date helper:

Sneak peak of training.thoughtbot.com's new date helper