Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Array() applied to hashes?

Tags:

arrays

ruby

hash

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.

like image 333
leifg Avatar asked Dec 11 '22 12:12

leifg


2 Answers

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):

  1. arg.to_ary
  2. arg.to_a
  3. and finally creates [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.

like image 76
Stefan Avatar answered Dec 20 '22 23:12

Stefan


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.

like image 23
leifg Avatar answered Dec 20 '22 23:12

leifg