Customize your Ruby classes with to_s and inspect

You just did an “extract class” refactor, and now your program’s output is all wrong. How do you fix it?

Take this simple program as a starting point:

print "What is your name? "
name = gets.strip
print "Hello ", name, "\n"

The program asks for your name, and says hello. Simple.

Did you know you can run Ruby programs from the command line, without saving them to a file first? Just run ruby from the command line, with no arguments. It will wait for input. Then paste your code and hit return. Finally, type ctrl-d to close the input. Ruby now runs the program, and all you had to do was a simple copy and paste!

Now you want to create your own class, to encapsulate the concept of a person in your application:

class Person
  def initialize(name)
    @name = name
  end
end

print "What is your name? "
person = Person.new(gets.strip)
print "Hello ", person, "\n"

Uh-oh! You get some hideous output: Hello #<Person:0x007ffdb9833600> (the random numbers will be different for you). What gives?

Work your way from the top-down to see what’s going on. The class is a straight-forward class definition. Line 7 simply prints a string to the screen. On line 8, you call gets.strip - what does it do?

According to the official Ruby documentation, gets returns a string object read from standard input, and then strip removes any leading and trailing whitespace (e.g. spaces, newlines).

The built-in print method prints a string representation of any objects that you pass to it. The first example works because gets returns a string object, so you don’t need to do anything special. But for other kinds of objects, you have a bit more work to do… print will convert a non-string object to a string using the object’s to_s method - shorthand for “to string”.

To customize how Ruby displays a Person object in this program, you need to define a custom to_s method:

class Person
  def initialize(name)
    @name = name
  end

  def to_s
    @name
  end
end

print "What is your name? "
person = Person.new(gets.strip)
print "Hello ", person, "\n"

This example just returns the string object stored in the @name instance variable… but with a custom to_s method, you can define any behavior that you want:

class Person
  def initialize(name)
    @name = name
  end

  def to_s
    case @name
    when "Oz"
      "Oz, the great and powerful"
    else
      @name
    end
  end
end

print "What is your name? "
person = Person.new(gets.strip)
print "Hello ", person, "\n"

Another way to to_s

In the examples so far, you’ve used print with multiple arguments. You can also convert an object to a string using the #{} operator inside of a double-quoted string:

print "Hello #{person}\n"

This is known as “string interpolation.”

Finally, you can simplify the last line by using puts instead of print. puts works the same as print, but automatically includes a newline character:

puts "Hello #{person}"

It’s not all about output…sometimes you need to debug

You’ve learned how to customize how your object displays on screen, but there’s one case where your custom to_s method will still fall short: when you have an error message that involves your object.

Run the following code, which will fail with a NoMethodError:

class Person
  def initialize(name)
    @name = name
  end

  def to_s
    case @name
    when "Oz"
      "Oz, the great and powerful"
    else
      @name
    end
  end
end

print "What is your name? "
person = Person.new(gets.strip)
person.foo

You’ll get an error message that looks something like: undefined method 'foo' for #<Person:0x007f8b122977b0 @name="Pat"> (NoMethodError). That looks kind of like a combination of the earlier, non-customized to_s, and the newly customized version. What’s going on now?

Ruby uses another method called inspect in situations where you might want to get a bit more info on the objects than the simple string representation you use for regular output.

Add a bit of private information that you don’t want to display all over the app… like a social security number:

class Person
  def initialize(name)
    @name = name
    @ssn = "111-11-1111"
  end

  def to_s
    @name
  end
end

print "What is your name? "
person = Person.new(gets.strip)
print "Hello ", person, "\n"

When the program says hello, it will hide the person’s social security number - which is what you probably want! But that extra information can come in really handy when you’re debugging. Define a custom inspect method, and Ruby will show you that extra information whenever you have an error involving that object:

class Person
  def initialize(name)
    @name = name
    @ssn = "111-11-1111"
  end

  def to_s
    @name
  end

  def inspect
    "Person(name: #{@name}, ssn: #{@ssn}, object_id: #{"0x00%x" % (object_id << 1)})"
  end
end

print "What is your name? "
person = Person.new(gets.strip)
person.foo

Now you have complete control over the debug output and get something like undefined method 'foo' for Person(name: Pat, ssn: 111-11-1111, object_id: 0x007fddfb2171f0):Person (NoMethodError).

The awesome_print gem provides a really nifty way of printing entire object structures in a readable way, so you don’t even have to create your own custom method.

Wrap up

Output plays a big role in any application, and knowing the different ways you can customize an object’s output will help you write programs that work the way you want, and help you with debugging.

Go ahead and customize your own Ruby classes with to_s and inspect!

"How do I become a better Ruby developer?"

Blogs, books, and bootcamps all promise to make you a better Ruby developer, but end up confusing you more.

What if you had step-by-step instructions on how to become a better Ruby developer?

Enter your name and email below and I’ll show you how to...

  • get better at Ruby in just five minutes each day
  • use testing, OOP, and refactoring to write professional-level Ruby
  • identify and learn new programming skills quickly