简体   繁体   中英

Why isn't the puts method calling my .to_s method?

I thought that defining a to_s method for a custom class meant that calling the puts method on that class would return an output as specified by to_s . In this program, however, I only get the result I crave if I write puts bingo_board.to_s . What is going on?

class BingoBoard < Array
  @@letters = %w[B I N G O]

  def initialize
    # populates an 5x5 array with numbers 1-100
    # to make this accessible across your methods within this class, I made
    # this an instance variable. @ = instance variable
    @bingo_board = Array.new(5) {Array.new(5)}
    @bingo_board.each_with_index do |column, i|
      rangemin = 15 * i + 1
      @bingo_board[i] = (rangemin..(rangemin+14)).to_a.sample(5)
    end
    @bingo_board[2][2] = "X" # the 'free space' in the middle
    @game_over = false
  end

  def game_over?
    @game_over
  end

  def generate_call
    ....
  end

  def compare_call(call)
    @bingo_board[@@letters.index(call[0])].include? call[1]
  end

  def react_to_call(call)
    ...
  end

  def check_board
    ...
  end

  def show_column(num)
    ...
  end

  def to_s
    result = ""
    0.upto(4) do |val|
      result += " " + @@letters[val] + " "
    end
    result += "\n\n"
    0.upto(4) do |row|
      0.upto(4) do |col|
        val = @bingo_board[col][row]
        result += " " if val.to_i < 10
        result += val.to_s + " "
      end
      result += "\n"
    end
    result
  end
end

my_board = BingoBoard.new
counter = 0
until my_board.game_over?
  puts my_board.to_s # renders the board in accordance with my to_s method
  call = my_board.generate_call
  counter += 1
  puts "\nThe call \# #{counter} is #{call[0]} #{call[1]}"
  my_board.react_to_call(call)
  gets.chomp
end
puts my_board  # renders bubkes (i.e., nothing)
puts "\n\n"
puts "Game over"

Its because you'r extending from Array. That's why you're getting the wierd behavior. I don't see where you need the extending from so just remove that and things will work as you expect.

Here's a more detaled answer if you'd like to know why this is happening. Basically puts makes an exception for arrays so when an array is passed puts is called on each member. Ruby Array#puts not using overridden implementation?

As @jörgwmittag said, this is a special case. The IO#puts method treats arrays - which means anything that responds to to_ary - differently. It calls to_ary first and then iterates over each element of the resulting array, and only calls to_s on them. It never calls to_s on the array itself.

If you delegate to a member array instead of subclassing from Array , you have finer-grained control over what gets "inherited" (delegated). Then you can exclude to_ary from the delegation, which will prevent puts from seeing your object as an Array and triggering this behavior.

Other general solutions:

  1. Use string interpolation or explicit to_s calls so that what puts receives is already a string:

     puts "#{bingo_board}" puts bingo_board.to_s 
  2. Use print or printf instead of puts :

     print bingo_board,"\\n" printf "%s\\n",bingo_board 

If the object is an Array or can be converted to one (ie it implements to_ary ), then puts will not call to_s on the object, but rather iterate over the object and print each object within by calling to_s on it.

See:

puts [1, 2]
# 1
# 2

[1, 2].to_s
# => '[1, 2]'

This is actually documented , although somewhat implicitly:

If called with an array argument, writes each element on a new line.

Looks like it runs Array#inspect method instead of your custom to_s . Adding alias_method :inspect, :to_s just after ending of the to_s definition will help you.

But it'll work only with p , because puts runs each(&:inspect) .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM