Gerry Gleason
Gerry Gleason

Reputation: 381

In a Ruby Class that Inherits from String, How do I Get "#{my_string}" with explicit .to_s

In short, I expect all three #{}s in the final line of this test program to result in the same string and they don't. It works if my class inherits from Object.

> ./test.rb 
A string foobar, and MyString: , MyString.to_s: foobar

> cat test.rb 

    #!/usr/bin/env ruby

    class MyString < String
      def initialize s
        @s= s.to_s
        @simple = s.index(?+)
      end
      def to_s() @s end
    end

x='foobar'

puts "A string #{x}, and MyString: #{
        MyString.new(x)}, MyString.to_s: #{MyString.new(x).to_s}"

Upvotes: 1

Views: 2490

Answers (2)

matt
matt

Reputation: 79783

As Niklas B. demonstrates, string interpolation doesn't call to_s on String objects. The default value for the argument to String#initialize is "", so instances of your subclass are all in effect empty strings.

One way to get this to work is to call super in the initialize method if your subclass, and pass the string value:

class MyString < String
  def initialize s
    super s.to_s    # this line added
    @s= s.to_s
    @simple = s.index(?+)
  end
  def to_s() @s end
end

puts "#{MyString.new("foo")}"  # => produces "foo"

Upvotes: 2

Niklas B.
Niklas B.

Reputation: 95318

It seems like string interpolation handles objects of type String differently:

irb(main):011:0> class MyString < String
irb(main):012:1>   def initialize(str)
irb(main):013:2>     @str = str
irb(main):014:2>   end
irb(main):015:1>   def to_s
irb(main):016:2>     puts "to_s called!"
irb(main):017:2>     @str
irb(main):018:2>   end
irb(main):019:1> end
=> nil
irb(main):020:0> "#{MyString.new('foo')}"
=> ""

As you can see, to_s is not even called. The reason for that is the invariant str.to_s == str where str.is_a? String. You broke that invariant, thus confusing the library code.

Conclusion: Don't override to_s if you're subclassing String (it doesn't make a lot of sense anyway).

Upvotes: 4

Related Questions