Ruby: overriding the puts method
Asked Answered
D

4

7

I've got a small program meant to be run in IRB. It ultimately outputs something that looks like an array, though technically isn't an array. (The class inherits from array.) The problem is, when I do an instance of this class, e.g. example = Awesome.new(1,2,3), and I write "puts example", IRB's default behavior is to put each element of example onto it's own line.

So instead of

[1,2,3]

(which is what I want), IRB pops out this.

1
2
3 

Is there a smart way to override the puts method for this special class? I tried this, but it didn't work.

def puts
  self.to_a
end

Any idea what I'm doing wrong?

Update: So I tried this, but no success.

def to_s
  return self
end

So when I'm in IRB and I just type "example", I get the behavior I'm looking for (i.e. [1, 2, 3]. So I figured I could just to return self, but I'm still messing up something, apparently. What am I not understanding?

Dimitri answered 13/7, 2012 at 3:51 Comment(2)
Is there a reason why you need to override puts instead of just using p?Ackack
You are not returning a proper string from to_s, why not just generate your own string and return it? The method to solve the problem is in my post, also; we don't generally like when authors change the question asked after posting it, then it's better to leave it and make a new one.Laureate
A
4
def puts(o)
  if o.is_a? Array
    super(o.to_s)
  else
    super(o) 
  end  
end

puts [1,2,3] # => [1, 2, 3]

or just use p:

p [1, 2, 3] # => [1, 2, 3] 
Ac answered 13/7, 2012 at 5:43 Comment(0)
D
6

You should override to_s and it will be handled automatically, just remember to return a string from to_s and it will work as a charm.


Example snippet..

class Obj 
  def initialize(a,b)
    @val1 = a 
    @val2 = b 
  end 

  def to_s
    "val1: #@val1\n" +
    "val2: #@val2\n" 
  end 
end

puts Obj.new(123,321);

val1: 123
val2: 321
Deportation answered 13/7, 2012 at 3:54 Comment(0)
A
4
def puts(o)
  if o.is_a? Array
    super(o.to_s)
  else
    super(o) 
  end  
end

puts [1,2,3] # => [1, 2, 3]

or just use p:

p [1, 2, 3] # => [1, 2, 3] 
Ac answered 13/7, 2012 at 5:43 Comment(0)
A
1

You probably would be better off implementing to_ary method for your Array-like class. This method will be called by puts to obtain all the elements. I posted a snippet from one of my projects below

require 'forwardable'

class Path 
  extend Forwardable
  def_delegators :@list, :<<, :count, :include?, :last, :each_with_index, :map

  def initialize(list = [])
    @list = list
  end

  def to_ary
    self.map.each_with_index{ |e, i| "#{e}, step: #{i}" }
  end
end 
Agna answered 1/6, 2014 at 1:21 Comment(0)
F
1

None of the answers really talks generically about Overriding the Ruby puts method, which is this question's title. So ignoring Ben's specific question about arrays, and assuming you have a good grasp of Ruby scope, let's consider the general case:

First, a caution: You probably don't really need to override the puts method globally. There are very few good reasons to. Because if you define a puts method in the top-level, global scope, you're overriding the default puts functionality for all Ruby code that you load (e.g. your scripts, plus all the libraries your script requires.) That can easily have unexpected consequences. Imagine overriding raise - you can... but you should not.

Second: It's always nice to know what you're overriding -- another answer helpfully pointed out that method(:puts) shows that you're overriding Kernel.puts. You might want to call that from within your override. Or you can call super, which calls the method you've overridden.

Third: Take note of Kernel.puts behaviors: it can take multiple args, sometimes known as varargs. And it returns nil. So perhaps your override should do the same. I also notice it has an odd tendency to unwrap arrays (probably related to accepting varargs) - so I'll reproduce that behavior, just because.

If you really want to override puts globally, you might do something like:

# Prefix each line of my puts output (including strings with "\n")
def puts *args
  if (args.length>1) then
    args.each { |a| puts a }
  elsif (args[0].is_a?(Array)) then # unwrap arrays like Kernel.puts
    puts args[0]
  else
    lines = "#{ args[0] }".split("\n")
    lines.each { |line| Kernel.puts "PREFIX: #{ line }" }
  end
  return nil
end
Fishery answered 17/10, 2022 at 21:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.