Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby: why does puts call to_ary?

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:

  • http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary/
  • http://yehudakatz.com/2010/01/02/the-craziest-fing-bug-ive-ever-seen/

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
like image 392
Tom De Leu Avatar asked Jan 22 '12 11:01

Tom De Leu


1 Answers

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
like image 177
Aliaksei Kliuchnikau Avatar answered Oct 02 '22 07:10

Aliaksei Kliuchnikau