Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replacing the to_s method in Ruby. Not printing out desired string

Tags:

class

ruby

So, I am just beginning to learn Ruby and I included a to_s method in my Class so that I can simply pass the Object to a puts method and have it return more than just the Object ID. I made a mistake and defined it as such:

def to_s
    puts "I'm #{@name} with a health of #{@health}."
end

instead of:

def to_s
    "I'm #{@name} with a health of #{@health}."
end

So, when I do this while using the first code block:

player1 = Player.new("larry")
puts player1

I get an object ID and a string when I execute the above two lines of code and not just the string. Why is this? I get this output:

I'm Larry with a health of 90.
#<Player:0x007fca1c08b270>

I am trying to think about why the first version of the program doesn't just print out the string to console, but instead returns the object ID and the string. I thought that when I pass the object to puts, all that is happening is that puts turns around and calls the to_s method to get the player's string representation. Right?

like image 322
Jwan622 Avatar asked Oct 30 '25 19:10

Jwan622


2 Answers

When given arguments that are not strings or arrays puts calls rb_obj_as_string to turn its arguments into strings (see rb_io_puts)

If you search for rb_obj_as_string through the ruby codebase (I find http://rxr.whitequark.org useful for this) you can see it's defined as

VALUE rb_obj_as_string(VALUE obj)
{
  VALUE str;

  if (RB_TYPE_P(obj, T_STRING)) {
    return obj;
  }
  str = rb_funcall(obj, id_to_s, 0);
  if (!RB_TYPE_P(str, T_STRING))
    return rb_any_to_s(obj);
  if (OBJ_TAINTED(obj)) OBJ_TAINT(str);
  return str;
}

In brief this:

  • returns straightaway if the argument is already a string
  • calls to_s
  • if the result is not a string, call rb_any_to_s and return that.

rb_any_to_s is what implements the default "class name and id" result that you're seeing: for any object it returns a string of the form #<ClassName: 0x1234567890abcdef>

Returning to your code, when you run puts player1 it calls rb_obj_as_string to convert your player to a string.

This first calls your to_s method, which uses puts to output your message. Your method then returns nil (because that's what puts always returns) so ruby calls rb_any_to_s, and that is what the outermost puts ends up using.

like image 151
Frederick Cheung Avatar answered Nov 03 '25 00:11

Frederick Cheung


That's because the puts returns nil, so does that version of to_s:

def to_s
  puts "I'm #{@name} with a health of #{@health}."
end

With puts player1, player1.to_s method is called, which prints the String "I'm ...", but the return value is that of the puts call inside to_s, which is nil.

So player1 is an object of which to_s returns nil, thus puts player1 in the end prints the result of the inherited to_s method.

like image 21
ryenus Avatar answered Nov 03 '25 00:11

ryenus



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!