June 26, 2012
Following code was tested with ruby 1.9.3 .
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.
Before we look at to_str
let's see 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.
Let's 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 we 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
for 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.
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.
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 to 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
The practical usage of this example can be seen here.
irb(main):002:0> ["hello", "world"].join(1)
TypeError: no implicit conversion of Fixnum into String
In the above case ruby is refusing to invoke to_s
on "1" because it knows that
adding "1" to a string does not feel right.
However we can add method to_str
to Fixnum as shown in the last section and
then we will not get any error. In this case the result will be as shown below.
irb(main):008:0> ["hello", "world"].join(1)
=> "hello1world"
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 the result would be same whether you have defined to_s
or to_str
if you
doing puts
.
puts Path.new('world')
However in the following case just defining to_s
will cause error. Only by
having to_str
following case will work.
puts 'hello ' + Path.new('world')
So the difference between defining to_s
and to_str
is not just what you see
in the output.
If a class defines to_str
then that class is telling the world that although
my class is not String
you can treat me like a String
.
If this blog was helpful, check out our full blog archive.