Ruby - elegantly convert variable to an array if not an array already

RubyArrays

Ruby Problem Overview


Given an array, a single element, or nil, obtain an array - the latter two being a single element array and an empty array respectively.

I mistakenly figured Ruby would work this way:

[1,2,3].to_a  #= [1,2,3]     # Already an array, so no change
1.to_a        #= [1]         # Creates an array and adds element
nil.to_a      #= []          # Creates empty array

But what you really get is:

[1,2,3].to_a  #= [1,2,3]         # Hooray
1.to_a        #= NoMethodError   # Do not want
nil.to_a      #= []              # Hooray

So to solve this, I either need to use another method, or I could meta program by modifying the to_a method of all classes I intend to use - which is not an option for me.

So a Method it is:

result = nums.class == "Array".constantize ? nums : (nums.class == "NilClass".constantize ? [] : ([]<<nums))

The problem is that it is a bit of a mess. Is there an elegant way of doing this? (I would be amazed if this is the Ruby-ish way to solve this problem)


What applications does this have? Why even convert to an array?

In Rails' ActiveRecord, calling say, user.posts will either return an array of posts, a single post, or nil. When writing methods which work on the results of this, it is easiest to assume that the method will take an array, which may have zero, one, or many elements. Example method:

current_user.posts.inject(true) {|result, element| result and (element.some_boolean_condition)}

Ruby Solutions


Solution 1 - Ruby

[*foo] or Array(foo) will work most of the time, but for some cases like a hash, it messes it up.

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

[*[1, 2, 3]]    # => [1, 2, 3]
[*1]            # => [1]
[*nil]          # => []
[*{a: 1, b: 2}] # => [[:a, 1], [:b, 2]]

The only way I can think of that works even for a hash is to define a method.

class Object; def ensure_array; [self] end end
class Array; def ensure_array; to_a end end
class NilClass; def ensure_array; to_a end end

[1, 2, 3].ensure_array    # => [1, 2, 3]
1.ensure_array            # => [1]
nil.ensure_array          # => []
{a: 1, b: 2}.ensure_array # => [{a: 1, b: 2}]

Solution 2 - Ruby

With ActiveSupport (Rails): Array.wrap

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

If you are not using Rails, you can define your own method similar to the rails source.

class Array
  def self.wrap(object)
    if object.nil?
      []
    elsif object.respond_to?(:to_ary)
      object.to_ary || [object]
    else
      [object]
    end
  end
end

Solution 3 - Ruby

The simplest solution is to use [foo].flatten(1). Unlike other proposed solutions, it will work well for (nested) arrays, hashes and nil:

def wrap(foo)
  [foo].flatten(1)
end

wrap([1,2,3])         #= [1,2,3]
wrap([[1,2],[3,4]])   #= [[1,2],[3,4]]
wrap(1)               #= [1]
wrap(nil)             #= [nil]
wrap({key: 'value'})  #= [{key: 'value'}]

Solution 4 - Ruby

Array(whatever) should do the trick

Array([1,2,3]) # [1,2,3]
Array(nil) # []
Array(1337)   # [1337]

Solution 5 - Ruby

#ActiveSupport (Rails) ActiveSupport has a pretty nice method for this. It's loaded with Rails, so defiantly the nicest way to do this:

Array.wrap([1, 2, 3]) #=> [1, 2, 3]
Array.wrap(nil) #=> nil

#Splat (Ruby 1.9+) The splat operator (*) un-arrays an array, if it can:

*[1,2,3] #=> 1, 2, 3 (notice how this DOES not have braces)

Of course, without an array, it does weird things, and the objects you "splat" need to be put in arrays. It's somewhat weird, but it means:

[*[1,2,3]] #=> [1, 2, 3]
[*5] #=> [5]
[*nil] #=> []
[*{meh: "meh"}] #=> [[:meh, "meh"], [:meh2, "lol"]]

If you don't have ActiveSupport, you can define the method:

class Array
    def self.wrap(object)
        [*object]
    end
end

Array.wrap([1, 2, 3]) #=> [1, 2, 3]
Array.wrap(nil) #=> nil

Although, if you plan on having large arrays, and less non-array things, you might want to change it - the above method is slow with large arrays, and can even cause your Stack to Overflow (omg so meta). Anyways, you might want to do this instead:

class Array
    def self.wrap(object)
        object.is_a? Array ? object : [*object]
    end
end

Array.wrap([1, 2, 3]) #=> [1, 2, 3]
Array.wrap(nil) #=> [nil]

I also have some benchmarks with and without the teneray operator.

Solution 6 - Ruby

How about

[].push(anything).flatten

Solution 7 - Ruby

With the risk of stating the obvious, and knowing that this isn't the most tasty syntactic sugar ever seen on the planet and surrounding areas, this code seems to do exactly what you describe:

foo = foo.is_a?(Array) ? foo : foo.nil? ? [] : [foo]

Solution 8 - Ruby

you can overwrite the array method of Object

class Object
    def to_a
        [self]
    end
end

everything inherits Object, therefore to_a will now be defined for everything under the sun

Solution 9 - Ruby

I've gone through all the answers and mostly don't work in ruby 2+

But elado has the most elegant solution i.e

> With ActiveSupport (Rails): Array.wrap > > Array.wrap([1, 2, 3]) # => [1, 2, 3] > > Array.wrap(1) # => [1] > > Array.wrap(nil) # => [] > > Array.wrap({a: 1, b: 2}) # => [{:a=>1, :b=>2}]

Sadly but this also doesn't work for ruby 2+ as you will get an error

undefined method `wrap' for Array:Class

So in order to fix that you need to require.

> require 'active_support/deprecation' > > require 'active_support/core_ext/array/wrap'

Solution 10 - Ruby

Since the method #to_a already exists for the two main problematic classes (Nil and Hash), just define a method for the rest by extending Object:

class Object
    def to_a
        [self]
    end
end

and then you can easily call that method on any object:

"Hello world".to_a
# => ["Hello world"]
123.to_a
# => [123]
{a:1, b:2}.to_a
# => [[:a, 1], [:b, 2]] 
nil.to_a
# => []

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionxxjjnnView Question on Stackoverflow
Solution 1 - RubysawaView Answer on Stackoverflow
Solution 2 - RubyeladoView Answer on Stackoverflow
Solution 3 - RubyoliView Answer on Stackoverflow
Solution 4 - RubyBenjamin GruenbaumView Answer on Stackoverflow
Solution 5 - RubyBen AubinView Answer on Stackoverflow
Solution 6 - RubyBruno MeiraView Answer on Stackoverflow
Solution 7 - RubyPelleView Answer on Stackoverflow
Solution 8 - RubyrunubView Answer on Stackoverflow
Solution 9 - RubyMalware SkiddieView Answer on Stackoverflow
Solution 10 - RubyShoeView Answer on Stackoverflow