Rails comes with built in support for easily customizing the to_s method. It’s something that’s pretty well documented in the source, but rarely used in practice, which is unfortunate, because the common alternative approach is a bit messy:
1 2 |
Date.today.strftime('%B %e, %Y') #=> "2008-02-29" |
A small, but useful improvement:
1 2 |
Date.today.to_s(:long) #=> "February 29, 2008" |
The underlying implementation is straightforward, with some classes (Date, Time, DateTime, and Range) allowing you to add custom formats. The advantage is all the formatting logic is kept in one place, and available via a unified interface, rather than defined randomly throughout the application.
Write a custom conversion format
Rolling a custom conversion is as easy as adding an entry to the classes’s format hash:
config/initializers/conversions.rb1 2 3 4 5 |
# Formats the time using strftime. # Example: Time.now.to_s(:event) #=> "03:23PM" Time::Conversions::DATE_FORMATS.update(:event => '%I:%M%p') |
In addition to strings, lambdas are also supported. If the value is a callable object instead of a string, the result of that call will be returned:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# Formats the date using a lambda. # Example: Date.today.to_s(:event) #=> "February 29th" date_formats = { :event => lambda { |date| date.strftime("%B #{date.day.ordinalize}") } } Date::Conversions::DATE_FORMATS.update(date_formats) # Examples: # (1.day.ago..5.days.from_now).to_s(:event) #=> "February 28th to March 5th" # (1.hour.ago..3.hours.from_now).to_s(:event) #=> "3:10PM to 7:10PM" range_formats = { :event => lambda do |start, stop| [ start.to_s(:event), stop.to_s(:event) ].join(" to ") end } Range::Conversions::DATE_FORMATS.update(range_formats) |
A common situation is where both the Date and Time objects should share the same formats. Keeping the format definitions in the same initializer makes this easy:
1 2 3 4 5 6 7 8 |
date_formats = { :event => lambda { |date| date.strftime("%B #{date.day.ordinalize}") },
:story => ... }
Date::Conversions::DATE_FORMATS.update(date_formats)
Time::Conversions::DATE_FORMATS.update(date_formats)
# DateTime uses Time's DATE_FORMATS, so there's nothing to update for it.
|
A note on naming
Coming up with an easy to remember, expressive name for formats is a bit challenging. The default Rails formats take the approach of trying to describe the result in their names (:short, :long, :long_ordinal). In larger projects, it’s difficult to remember what each format does and where it should be used.
A naming system that’s working a bit better are formats named after their intended use case (:event, :blog, :hours, :event_hours, etc.)

