Frerich Raabe
Frerich Raabe

Reputation: 94409

How can I make a custom Ruby type behave like a string?

If I have a custom Ruby class representing some string type, as in

class MyString
end

Which functions should I implement in order to make the following use cases possible:

  1. Passing a Ruby string whenever a MyString is expected
  2. Passing a MyString whenever a Ruby string is expected
  3. Comparing a Ruby string with a MyString value (it shouldn't matter whether I use s == t or t == s).

I saw various interesting functions like to_s, cmp, == and eq already, but it's not clear to me when each of them is called.

My concrete use case is that I'm writing a Ruby extension using the C API which exposes functions taking (and returning) values of a custom string type (QString, to be precise) which my extension also registers. However, I'd like to make those custom strings behave as intuitive as possible. Unfortunately I can't just return Ruby strings from my C code since it should be possible to call Qt methods on the strings.

Upvotes: 3

Views: 1550

Answers (4)

Yann
Yann

Reputation: 45

Since I was looking for something similar, but none of the other answers worked for me, I'll post what did work for me.

Found in this blog post which discourage the use of inheriting String and instead use simple delegator.

Inheriting from SimpleDelegator create an object which delegate everything to a string of your choice but on which you add behavior as you see fit.

class ChunkyBacon < SimpleDelegator
  def initialize(content)
    @content = content
    super @content
  end

  def chunky_bacon?
    @content == 'chunky_bacon'
  end
end

test = ChunkyBacon.new('choco pizza') # => 'choco pizza'
test.chunky_bacon? # => false

Upvotes: 0

DigitalRoss
DigitalRoss

Reputation: 146123

There are at least three approaches:

  1. class MyString < String; ...; end
  2. Define #to_s
  3. Define #to_str

Doing both #2 and #3 will make the object act very much like a real String even if it isn't a subclass.

#to_s is an explicit converter, meaning it must appear in Ruby code to work.

#to_str is an implicit converter, meaning the Ruby interpreter will attempt to call it when it wants a String but is given something else.

Update:

Here is an example of some fun you can have with to_str:

begin
  open 1, 'r'
rescue TypeError  => e
  p e
end
class Fixnum
  def to_str; to_s; end
end
open 1, 'r'

When run, the first open fails with TypeError but the second proceeds to looking for 1.

#<TypeError: can't convert Fixnum into String>
fun.rb:9:in `initialize': No such file or directory - 1 (Errno::ENOENT)
    from fun.rb:9:in `open'

Upvotes: 6

Andy
Andy

Reputation: 1490

It's not generally a good idea to subclass classes that were built by someone else in Ruby, because too many things can go wrong. (You might, for example, override an internal method without knowing it.)

1) define Mystring.to_s to get automatic conversion from a Mystring to a String.

2) Not sure what you mean by this. If you want a String method that returns a Mystring, you will have to monkey-patch String:

Class String
  def to_mystring
    return Mystring.new(self)
  end
end

3) to get t == s (assuming s is an instance of String and t an instance of Mystring) define <=>. To get s == t you will have to monkey patch String again, though.

Upvotes: 0

tadman
tadman

Reputation: 211670

Although it's tempting to sub-class String to give it a new initialize method that will import these QString-type strings, you may just want to tack on an extension to String that helps with the conversion so you don't have to re-implement a version of String itself.

For instance, with two methods you could pretty much have this done:

class String
  def self.from_qstring(qstring)
    new(...)
  end

  def to_qstring
    # ...
  end
end

Having multiple storage types for String is not going to be a problem until you start comparing them, but given that Ruby's String is quite robust, writing a work-alike is difficult.

Upvotes: 1

Related Questions