Ruby: why does puts call to_ary?
Asked Answered
H

1

13

I'm learning metaprogramming in Ruby and am just trying out defining missing methods via method_missing and define_method. I'm getting some unexpected behaviour and am wondering if anyone can explain this. Here is my class:

class X
  def method_missing(m, *args, &block)
    puts "method #{m} not found. Defining it."
    self.class.send :define_method, m do
      puts "hi from method #{m}"
    end
    puts "defined method #{m}"
  end  
end

Now, this code:

x = X.new

x.some_method
puts
x.some_method
puts
puts x

Produces the output:

method some_method not found. Defining it.
defined method some_method

hi from method some_method

method to_ary not found. Defining it.
defined method to_ary
#<X:0x007fcbc38e5030>

What I don't get is the last part: why is Ruby calling to_ary in a call to puts? Why would Ruby try to convert my object into an array just to print it?

I've Googled around and found these related links:

These also talk about method_missing and to_ary gotchas, but not specifically about why puts would call to_ary.

I should also mention that the behaviour does not change when I define a to_s, e.g.

def to_s
  "I'm an instance of X"
end

The output of "puts x" is then:

method to_ary not found. Defining it.
defined method to_ary
I'm an instance of X
Hoary answered 22/1, 2012 at 11:22 Comment(0)
C
16

puts is a synonym for $stdout.puts. $stdout is an IO class, so look at the documentation for IO.puts:

Writes the given objects to ios as with IO#print. Writes a record separator (typically a newline) after any that do not already end with a newline sequence. If called with an array argument, writes each element on a new line.

This mean that puts method is intended to write several lines of output. Thus it tries to call to_ary method on an object and if to_ary is defined, then prints each element of the returned Array on a new line, else puts calls to_s method.

to_ary internal usage is really not well documented in the Ruby documentation (Matz points this out in his The Ruby Programming Language book).

Methods print and p on the other hand don't call to_ary, only to_s.

Sidenote: Interesting, that to_ary must return real Array object, not an object defining each method or something else:

class Test
  def to_ary
    10.downto(1)
  end
end

puts Test.new

#TypeError: can't convert Test to Array (Test#to_ary gives Enumerator)
#        from (irb):28:in `puts'
#        from (irb):28:in `puts'
#        from (irb):28
Cupboard answered 22/1, 2012 at 11:44 Comment(1)
Thanks. I think the essence is "to_ary internal usage is really not well documented in the Ruby documentation" :) I just read the IO.puts docs, they do not explicitly mention to_ary, this should be clearer I think. Thanks for pointing to "The Ruby Programming Language" book, might check that out.Hoary

© 2022 - 2024 — McMap. All rights reserved.