A Ruby Conversion Idiom

@CapnKernul writes:

[do you know of] a Ruby idiom for converting an object to a type if it isn’t already that type. For example, if you want to only store an attribute of type Foo, you could write an accessor method that would pass any non-Foo object to Foo’s constructor.

There is a de facto idiom among Ruby built-ins to define a method with the same name as a class for doing conversions to that class. My favorite is Kernel#Array:

Array("foo")                    # => ["foo"]
Array([1,2,3])                  # => [1, 2, 3]
Array(nil)                      # => []
Array({:a => 1, :b => 2})       # => [[:a, 1], [:b, 2]]

But there are others. Kernel#Integer provides a stricter form of integer conversion than does #to_i:

"42".to_i                       # => 42
Integer("42")                   # => 42
"30 seconds".to_i               # => 30
Integer("30 seconds")           # =>
# ~> -:4:in `Integer': invalid value for Integer(): "30 seconds" (ArgumentError)
# ~> from -:4:in `<main>'

And there are also Kernel#String, Kernel#Float, Kernel#Complex, and Kernel#Rational:

String(123)                     # => "123"
Float("123")                    # => 123.0
Rational(4,5)                   # => (4/5)
Complex(1,3)                    # => (1+3i)

You can also find this in standard libraries. The URI library defines an idempotent conversion method for URIs:

require 'uri'

u = URI("http://example.com")   # => #<URI::HTTP:0x000000030cac80 URL:http://example.com>
URI(u)                          # => #<URI::HTTP:0x000000030cac80 URL:http://example.com>

There’s also Pathname:

require 'pathname'

p = Pathname("~/.emacs.d")      # => #<Pathname:~/.emacs.d>
Pathname(p)                     # => #<Pathname:~/.emacs.d>

I like to extend this idiom into my own code for idempotent conversions into commonly-used value objects. I did this in Objects on Rails for TagList objects.

module Conversions
  private
  def TagList(value)
    return value if value.is_a?(TagList)
    TagList.new(value)
  end
end

The TagList method is shorter than calling TagList.new(...). And in addition, it’s safe to call on inputs which might or might not already be TagList objects.

Although they may look a little odd at first, capitalized conversion methods are a well-established Ruby idiom for methods which “do the right thing” to convery any reasonable input value into a desired class. And because unlike #to_x methods they are outside of the objects being converted, they incur none of the maintenance danger of a monkey-patch. I don’t define them for every class in my program; but for oft-used value object types they can be quite convenient.

This entry was posted in Ruby and tagged , , . Bookmark the permalink.
  • petejohanson

    I’ve always been intrigued by the syntax for the hash conversion syntax, e.g. Hash["a", 100, "b", 200], which seems to serve the same role, but uses square brackets for the conversion.

    • http://henrik.nyh.se Henrik N

      `Dir` also has a `Dir.[]`, basically a `Dir.glob` shortcut. Not sure if there’s a convention there. 

      • http://avdi.org Avdi Grimm

        Unlike Hash, I think Dir#[] actually makes sense. The implication of square brackets is to “find” or “select” some object or subset. In this case, Dir["*.rb"] means “select the subset of filenames corresponding to this glob pattern”.

    • http://iain.nl iain

      I like this too, especially since you can define it on the class itself. Seems more contained this way.

    • http://avdi.org Avdi Grimm

      I rather dislike the Hash syntax. It feels like an abuse of square brackets to me. I realize Ruby plays fast and loose with square brackets, but I feel like in general they imply a “finder” or “selector” method: “find the object matching this pattern”.

      • petejohanson

        Yeah, it surprised me when I first saw it, for that same reason of not matching the other uses of square brackets elsewhere in Ruby.

  • snoobabk

    I’m not a ruby programmer but if I understand the scenarios above I mostly try follow the 2 conversion idioms that Kent Beck wrote in his Smalltalk Best Practice Patterns book.

    For objects that share the same protocol but different format create a conversion method on the object and prepend ‘as’ to the class of the returned object. e.g. Collection>>asSet

    For conversion of an object to another object with a different protocol make a creation converter method that takes the object to be converted e.g. Date class>>fromString:

    This being said I’d stick with the current Ruby idioms if they are well understood as changing it would only bring confusion, but on a team if you agree on the convention then I like the 2 idioms that Kent introduced.