Needing help with which to_s gets called

I’m a beginner learning Ruby and I’ve hit something which I can’t find
an explanation for in my text books.

If I run the following code:

class Able < Array

def initialize(contents)

@contents = Array.new(contents)

end

def to_s
“[Able: #{self.join(”, “)}]”
end
end

class Baker
def initialize(contents)
@contents = Array.new(contents)
end

def <<(item)
@contents << item
end

def to_s
“[Baker: #{@contents.join(”, “)}]”
end
end

item = Baker.new([“a”, “b”])
item << Able.new([“c”, “d”])
item << “e”
puts item

I get the following output:

[Baker: a, b, c, d, e]

Clearly the to_s method in my Able class is not being called. If
however I change the definition of Able to:

class Able
def initialize(contents)
@contents = Array.new(contents)
end

def to_s
“[Able: #{@contents.join(”, “)}]”
end
end

(Note that it no longer inherits from Array and instead contains an
array as an instance variable.)

I now get this output (which is what I wanted in the first place).

[Baker: a, b, [Able: c, d], e]

Why when Able is a sub-class of Array does my to_s method get ignored?

TIA,
Joh

P.S. I tried to read the FAQ as instructed first but the link given for
it gives a “Not found” error.

John W. wrote:

Why when Able is a sub-class of Array does my to_s method get ignored?

Because puts special cases arrays, not calling to_s on them at all.
If you compare puts [1,2,3] to puts [1,2,3].to_s you will see
that this is the case (puts [1,2,3] is just like puts 1,2,3 printing
each number on its own line instead of having it all in one line like
you get with to_s.

HTH,
Sebastian

Sebastian H. wrote:

John W. wrote:

Why when Able is a sub-class of Array does my to_s method get ignored?

Because puts special cases arrays, not calling to_s on them at all.
If you compare puts [1,2,3] to puts [1,2,3].to_s you will see
that this is the case (puts [1,2,3] is just like puts 1,2,3 printing
each number on its own line instead of having it all in one line like
you get with to_s.

Thanks for that. There certainly seems to be some sort of
special-casing going on, but it’s more widespread than just puts.

I tried the test case which you suggested and see exactly what you said.
However I also went back to my original code and changed the “puts” line
from:

puts item

to:

output = item.to_s
puts output

and I still get the same oddity so it’s clearly not just puts doing it.

Do you have a reference where I can read up more on this?

TIA,
John

On Thu, Jul 31, 2008 at 2:31 PM, John W. [email protected]
wrote:

Thanks for that. There certainly seems to be some sort of
output = item.to_s

output is an instance of Baker so Ruby will invoke Baker’s to_s method.


“Hey brother Christian with your high and mighty errand, Your actions
speak
so loud, I can’t hear a word you’re saying.”

-Greg Graffin (Bad Religion)

John W. wrote:

Thanks for that. Â There certainly seems to be some sort of
special-casing going on, but it’s more widespread than just puts.

You’re right. join special cases arrays as well, joining nested arrays
as well
instead of to_sing them (that or it just calls flatten on the array
before
starting it’s work).

HTH,
Sebastian

On Thu, Jul 31, 2008 at 2:35 PM, Glen H. [email protected]
wrote:

you get with to_s.
to:

Actually now that I think about it a little more it appears that in the
first case something a little odd is happening in that Ruby is just
treating
the Able data as a normal array but as soon as you assign the data to an
instance variable is adds an instance of Able to the array rather than
just
passing the array values through.


“Hey brother Christian with your high and mighty errand, Your actions
speak
so loud, I can’t hear a word you’re saying.”

-Greg Graffin (Bad Religion)

On Thu, Jul 31, 2008 at 4:41 PM, Sebastian H.
[email protected] wrote:

John W. wrote:

Thanks for that. There certainly seems to be some sort of
special-casing going on, but it’s more widespread than just puts.

You’re right. join special cases arrays as well, joining nested arrays as well
instead of to_sing them (that or it just calls flatten on the array before
starting it’s work).

I guess this is an example of core class special cases that I was
worried about in an earlier thread today.

I really don’t think it’s a good practice to subclass core classes
unless you are only adding new behaviour.
Wrapping and delegating is much more predictable.

That having been said, the problem goes away if you first explicitly
map using to_s before the join(), but this is an indication of the
kind of hacks subclassing core objects like Array will force upon you
:-/

def to_s
“[Baker: #{@contents.map { |e| e.to_s }.join(”, “)}]”
end

Thanks to all for some very useful pointers.

On Thu, Jul 31, 2008 at 4:41 PM, Sebastian H.
[email protected] wrote:
[snip]

You’re right. join special cases arrays as well, joining nested arrays as well
instead of to_sing them (that or it just calls flatten on the array before
starting it’s work).

That certainly seems to be the case, as witnessed by the following
snippet.

a = [“a”, “b”, “c”]
b = [“d”, “e”, “f”]
c = Array.new
c << a
c << b
puts “Size of c is #{c.size}”
puts c.join(", ")

which produces:

Size of c is 2
a, b, c, d, e, f

It looks very much like join will flatten anything which it thinks it
can.

Gregory B. wrote:

I guess this is an example of core class special cases that I was
worried about in an earlier thread today.

I really don’t think it’s a good practice to subclass core classes
unless you are only adding new behaviour.
Wrapping and delegating is much more predictable.

Thank you - I’ll take note of that. My original problem was that I have
a structure involving nested arrays and I wanted them to print out with
the nesting apparent. Sub-classing Array seemed the obvious way to do
it, but perhaps not.

That having been said, the problem goes away if you first explicitly
map using to_s before the join(), but this is an indication of the
kind of hacks subclassing core objects like Array will force upon you
:-/

def to_s
“[Baker: #{@contents.map { |e| e.to_s }.join(”, “)}]”
end

Adding to the snippet above:

puts c.map { |e| e.to_s }.join(", ")

Produces:

abc, def

So I now have a choice of two ways to do it.

Cheers,
John