Recently I stumbled upon "The Elements of Style in Ruby #3: Make Sure Something Is an Array".
TL;DR: The Method Array()
in Ruby converts anything you put in, into an array and tries to guess what the correct outcome should be:
Array(1) # => [1]
Array([1,2]) # => [1,2]
Array(nil) # => []
So what does Array({a: :b})
return? I would assume it returns an array with the hash as a value: [{a: :b}]
.
However, the hash is converted directly into an array: [:a, :b]
.
I don't just want to put a hash into an array ([{a: :b}]
). I'd like to have functionality that returns an array no matter what I put in. Array()
already does that, but it converts a hash to an array in a way that I don't expect.
So, basically the functionality should look like this:
NewFancyArrayMethod({a: :b}) # => [{a: :b}]
NewFancyArrayMethod([{a: :b}, {c: :d}]) # => [{a: :b}, {c: :d}]
The second part is already fulfilled by Array()
.
I know I can do something like values = [values] unless values.is_a? Array
, like the article points out. However, I would rather have a method that abstracts this conversion from me as Array()
already does. The only problem is that Array()
treats a hash differently than any other "single" value (String, Integer, Object, etc.). I simply don't want different handling for different cases.
So what does
Array({a: :b})
return? I would assume it returns an array with the hash as a value:[{a: :b}]
.
Your assumption is wrong. Kernel#Array
converts the argument arg
by trying (in that order):
arg.to_ary
arg.to_a
[arg]
Examples:
Array(1) #=> [1]
This is because of (3). There's no Fixnum#to_ary
or Fixnum#to_a
Array([1, 2]) #=> [1, 2]
This doesn't return [[1, 2]]
because of (1). Array#to_ary
returns self
Array(nil) #=> []
This doesn't return [nil]
because of (2). There's no NilClass#to_ary
but there's NilClass#to_a
: "Always returns an empty array."
Array({a: :b}) #=> [[:a, :b]]
Like Array(nil)
this doesn't return [{a: :b}]
because of (2). There's no Hash#to_ary
but there's Hash#to_a
: "Converts hsh to a nested array of [ key, value ] arrays."
Array(Time.now) #=> [33, 42, 17, 22, 8, 2013, 4, 234, true, "CEST"]
This is Time#to_a
returning ... "a ten-element array of values for time"
Conclusion:
Kernel#Array
works as expected, Hash#to_a
is the culprit. (or to_a
in general)
I'd like to have a functionality, that returns an array no matter what I put in.
Hash
and Array
are different objects. You could check the type (Kernel#Array
does this, too):
def hash_to_array(h)
h.is_a?(Array) ? h : [h]
end
Or extend Hash
(or even Object
) and Array
:
class Hash # or Object
def my_to_a
[self]
end
end
class Array
def my_to_a
self
end
end
See the comments for alternative implementations.
As Frederick Cheung pointed out in a commend, the method Array#wrap does exactly what I desire:
Array.wrap({a: :b})
# => [{a: :b}]
Unfortunately this method is only part of ActiveSupport, so it can't be used in plain Ruby projects.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With