Bryan Larsen Bryan Larsen - 5 months ago 7x
Ruby Question

Elegant way of duck-typing strings, symbols and arrays?

This is for an already existing public API that I cannot break, but I do wish to extend.

Currently the method takes a string or a symbol or anything else that makes sense when passed as the first parameter to


I'd like to add the ability to send a list of strings, symbols, et cetera. I could just use
is_a? Array
, but there are other ways of sending lists, and that's not very ruby-ish.

I'll be calling
on the list, so the first inclination is to use
respond_to? :map
. But a string also responds to
, so that won't work.


How about treating them all as Arrays? The behavior you want for Strings is the same as for an Array containing only that String:

def foo(obj, arg)
  [*arg].each { |method| obj.send(method) }

The [*arg] trick works because the splat operator (*) turns a single element into itself or an Array into an inline list of its elements.


This is basically just a syntactically sweetened version or Arnaud's answer, though there are subtle differences if you pass an Array containing other Arrays.

Later still

There's an additional difference having to do with foo's return value. If you call foo(bar, :baz), you might be surprised to get [baz] back. To solve this, you can add a Kestrel:

def foo(obj, arg)
  returning(arg) do |args|
    [*args].each { |method| obj.send(method) }

which will always return arg as passed. Or you could do returning(obj) so you could chain calls to foo. It's up to you what sort of return-value behavior you want.