Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accepting a parameter either as individual object or as array of objects

I have a bunch of functions that take in either an array of objects, or a single object designed to be treated as an array containing one object, and I am looking for a cleaner way to accomplish this. Basically, I want to know how I could make the unless part in the following function more concise:

def foo(bar_or_bars)
  unless bar_or_bars.is_a?(Array)
    bar_or_bars = [bar_or_bars]
  end
  bar_or_bars.each { |baz| ... }
end

Any help will be appreciated! Thanks.

like image 726
user2398029 Avatar asked Feb 28 '12 02:02

user2398029


3 Answers

The simplest solution is to use Kernel method Array:

Array(5) #=> [5]
Array([1, 2, 3]) #=> [1,2,3]

so

def foo(bar_or_bars)
  Array(bar_or_bars).each { |bar| ... }

This will even work on nested arrays that have arrays as elements (they wont be flattened out).

One case this won't work for is Hashes:

Array(a: 1, b: 2) #=> [[:a, 1], [:b, 2]]

If you want to iterate through unchanged Hash objects, best use Array.wrap from ActiveSupport.

like image 88
Nikola Avatar answered Oct 16 '22 19:10

Nikola


First thing you could do is to write the unless logic in a single line:

bars = bar_or_bars.is_a?(Array) ? bar_or_bars : [bar_or_bars]

As you see, I give it a new name here, as it's no longer a bar or bars, it's now definitely a collection.

The problem with this and your original approach is that although your function could work on any Enumerable, you will force your users to give you an argument of a specific type, which breaks duck typing.

A neat trick to partially solve that issue is the following:

def foo(bar_or_bars)
  bars = [*bar_or_bars]
  bars.each { |baz| ... }
end

I wouldn't exactly call that readable, though. It actually smells a lot like bad API design. Probably you should better take multiple arguments like this:

def foo(*bars)
  bars.each { |baz| ... }
end

And let the caller decide whether he wants to pass a single object or an array:

foo("XYZ")
ary = ["abc", "def"]
foo(*ary)
like image 16
Niklas B. Avatar answered Oct 16 '22 19:10

Niklas B.


I am currently using this:

bars = [bar_or_bars].flatten
like image 2
Spec Avatar answered Oct 16 '22 18:10

Spec