Reputation: 2665
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?
Upvotes: 7
Views: 5574
Reputation: 18986
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
Upvotes: 1
Reputation: 5329
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]
Upvotes: 4
Reputation: 8833
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
Upvotes: 1
Reputation: 63797
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.
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
Upvotes: 6