Don't Forget About to_s

In a previous article I talked using inspect to make using IRb as easy as possible and it reminded me that there is another method that is implicitly used by every Ruby developer, possibly hundreds of times a day, but which few ever define on their objects. That method is to_s.

Every time you see string interpolation (whenever you use #{object} in a string, or <%= object %> in Rails) you’re using to_s, every time you use puts you’re using to_s. And yet implementing to_s is often never done.

Before I start let’s use an example class of a Person with a first name, last name, date of birth and postal address.

1 class Person
2   attr_accessor :first_name, :last_name, :date_of_birth, :postal_address
3 end

Why

Why in fact do I need to implement to_s anyway? The implied use of to_s in string interpolation and with puts is mainly for readability. It allows us to directly refer to an object in a certain context and not have to describe exactly what we want in that context. With string interpolation

1 <formset title="Edit details for <%= person %>">

will read a lot better than

1 <formset title="Edit details for <%= person.first_name %> <%= person.last_name %>">

What

In the above example I used a simple to_s implementation

1 class Person
2   def to_s
3     "#{ @first_name } #{ @last_name }"
4   end
5 end

and in general, implementations of to_s should provide a simple label or representation of your data, suitable for end users, output on a single line. How’s that for output restriction!

In our previous article dealing with inspect I showed that Date#inspect was pretty opaque to someone not interested in debugging the underlying data structures, however Date#to_s is transparent:

1 >> Date.today
2 => #<Date: 4909485/2,0,2299161>
3 >> Date.today.to_s
4 => "2008-10-03"

as we would expect from a format that is designed to be totally human readable.

How

Ruby’s builtin data structures, Array and Hash, output all their contained data in a single line when to_s is called on them.

1 >> [1,2,3,4].to_s
2 => "1234" # From Ruby 1.9 onwards this will be "[1,2,3,4]"
3 >> {:a => 1, :b => 2}.to_s
4 => "a1b2" # And this will be "{:a => 1, :b => 2}"

For more complex data types outputting the entire object into a short string representation is not always possible so instead it’s better to refer to it by a natural identifier or labelling of some sort. For our Person class described earlier we just used the first and last names of the person represented by the object and to make it more specific we could add the age of the person in years like so

1 class Person
2   def to_s
3     "#{ @first_name } #{ @last_name }(#{ Date.today.year - @date_of_birth.year + ( @date_of_birth.yday < Date.today.yday ? 1 : 0 ) })"
4   end
5 end

That implementation produces the following output on line 4:

1 >> p
2 => #<Person:0xb7a9a710 @first_name="Joe", @postal_address="10 Main Street\nSmalltown\nUSA", @date_of_birth=#<Date: 4885443/2,0,2299161>, @last_name="Public">
3 >> p.to_s
4 => "Joe Public(33)"

Even though the postal_address is defined for the person it’s pointless trying to include it. To format an address correctly would take multiple lines and wouldn’t make sense being included on a single line output. In the case of an object like Person an identifier of the object, such as the first and last names, is sufficient. We added the age of the person to show how other data might be included but in my opinion it’s not required.

Because the requirements of to_s can change depending on the context of where it’s used, you might want to implement to_s so it takes an argument specifying the output format you want, which is what Rails does in its monkeypatching of the Date and Time classes. It’s pretty easy to implement yourself:

 1 class Person
 2   def to_s( format = nil )
 3     "#{ @first_name } #{ @last_name }" + 
 4       case format
 5         when :with_age
 6           "(#{ Date.today.year - @date_of_birth.year + ( @date_of_birth.yday < Date.today.yday ? 1 : 0 ) })"
 7         when :with_date_of_birth
 8           "(#{ @date_of_birth })"
 9         else
10           ""
11         end
12   end
13 end
1 >> p.to_s
2 => "Joe Public"
3 >> p.to_s( :with_age )
4 => "Joe Public(33)"
5 >> p.to_s( :with_date_of_birth )
6 => "Joe Public(1975-11-05)"

As with implementing inspect, when deciding how to implement to_s you need to be aware where you most use string interpolation and what you need from your object to either describe it in a compact form or identify it by some labelling.


Farrel Lifson is a lead developer at Aimred.

About Aimred

Aimred is a specialist Ruby and Ruby on Rails development house and consultancy based in Cape Town, South Africa.

We provide Ruby and Ruby on Rails development, consulting and training services to businesses and organisations of all sizes. If you want to find out how we can help you, contact us at info@aimred.com.

Recent Posts

Yearly Archives