Ruby - elegantly convert variable to an array if not an array already
RubyArraysRuby 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
# => []