Ruby: почему делает вызов to_ary?

Я изучаю метапрограммирование в Ruby и просто пытаюсь определить отсутствующие методы с помощью методов method_missing и define_method. Я получаю какое-то неожиданное поведение, и мне интересно, может ли кто-нибудь объяснить это. Вот мой класс:

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

Теперь этот код:

x = X.new

x.some_method
puts
x.some_method
puts
puts x

Производит вывод:

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>

То, что я не получаю, является последней частью: почему Ruby вызывает to_ary в вызове puts? Почему Ruby попытается преобразовать мой объект в массив только для его печати?

У меня Googled вокруг и нашел эти связанные ссылки:

Здесь также рассказывается о методах_имя и to_ary gotchas, но не о том, почему puts будет обращаться к to_ary.

Я также должен отметить, что поведение не меняется, когда я определяю значение to_s, например.

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

Результат "puts x":

method to_ary not found. Defining it.
defined method to_ary
I'm an instance of X

Ответ 1

puts является синонимом $stdout.puts. $stdout - это класс IO, поэтому посмотрите документацию IO.puts:

Записывает данные объекты в ios, как с печатью ввода-вывода #. Записывает запись разделитель (обычно это новая строка) после того, что еще не заканчивается последовательность новой строки. Если вызывается с аргументом массива, записывается каждый элемент на новой строке.

Это означает, что метод puts предназначен для записи нескольких строк вывода. Таким образом, он пытается вызвать метод to_ary для объекта и если to_ary определен, то печатает каждый элемент возвращенного Array в новой строке, иначе puts вызывает метод to_s.

to_ary внутреннее использование в документах Ruby действительно недостаточно хорошо документировано (Matz указывает на это в книге "Язык программирования Ruby" ).

Методы print и p, с другой стороны, не вызывают to_ary, только to_s.

Sidenote: Интересно, что to_ary должен возвращать реальный объект Array, а не объект, определяющий метод each или что-то еще:

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