to_str in ruby

Following code was tested with ruby 1.9.3 .

All objects have to_s method

to_s method is define in Object class and hence all ruby objects have method to_s.

Certain methods always call to_s method. For example when we do string interpolation then to_s method is called. puts invokes to_s method too.

class Lab
 def to_s
  'to_s'
 end
 def to_str
  'to_str'
 end
end

l = Lab.new
puts "#{l}" #=> to_s
puts l #=> to_s

to_s is simply the string representation of the object.

Why the failure

Before we look at to_str lets examine a case where ruby raises error.

e = Exception.new('not sufficient fund')

# case 1
puts e

# case 2
puts "notice: #{e}"

# case 3
puts "Notice: " + e

Here is the result

not sufficient fund
Notice: not sufficient fund
`+': can't convert Exception into String (TypeError)

In the first two cases the to_s method of object e was printed.

However in case ‘3’ ruby raised an error.

Lets read the error message again.

`+': can't convert Exception into String (TypeError)

In this case on the left hand side we have a string object. To this string object we are trying to add object e. Ruby could have called to_s method on e and could have produced the result. But ruby refused to do so.

Ruby refused to do so because it found that the object you are trying to add to string is not of type String. When we call to_s we get the string representation of the string. But the object might or might not be behaving like a string.

Here we are not looking for the string representation of e. What we want is e to behave a like string. And that is where to_str comes in picture. I have a few more examples to clear this thing so hang in there.

What is to_str

If an object implements to_str method then it is telling the world that my class might not be String but for all practical purposes treat me like a string.

So if we want to make exception object behave like a string then we can add to_str method to it like this.

e = Exception.new('not sufficient fund')

def e.to_str
  to_s
end

puts "Notice: " + e #=> Notice: not sufficient fund

Now when we run the code we do not get any exception.

What would happen if Fixnum has to_str method

Here is an example where ruby raises exception.

i = 10
puts '7' + i #=> can't convert Fixnum into String (TypeError)

Here Ruby is saying that Fixnum is not like a string and it should not be added to String.

We can make Fixnum behave like a string by adding a to_str method.

class Fixnum
  def to_str
    to_s
  end
end
i = 10
puts '7' + i #=> 710

A real practical example of defining to_str

I tweeted about a quick lesson in to_s vs to_str and a few people asked me to expand on that. Lets see what is happening here.

Before the refactoring was done Path is a subclass of String. So it is String and it has all the methods of a string.

As part of refactoring Path is no longer extending from String. However for all practical purposes it acts like a string. This line is important and I am going to repeat it. For all practical purposes Path here is like a String.

Here we are not talking about the string representation of Path. Here Path is so close to String that practically it can be replaced for a string.

So in order to be like a String class Path should have to_str method and that’s exactly what was done as part of refactoring.

During discussion with my friends someone suggested instead of defining to_str tenderlove could have just defined to_s and the result would have been same.

Yes in that case the result you see on console when you do

puts Path.new('world')

would be same but if you do not define to_str then following code would fail.

puts 'hello ' + Path.new('world')

So the difference between defining to_s and to_str is not just what you see in the output.

Conclusion

If a class defines to_str then that class is telling the world that altought my class is not String you can treat me like a String.