Hash Destructuring
Solution 1
Yes, there is a way to de-structure a hash:
def f *args; args; end
opts = {hash2: 'bar', hash3: 'baz'}
f *opts #=> [[:hash2, "bar"], [:hash3, "baz"]]
The problem is that you what you want is actually not de-structuring at all. You’re trying to go from
'arg1', { hash2: 'bar', hash3: 'baz' }, { hash1: 'foo' }
(remember that 'arg1', foo: 'bar'
is just shorthand for 'arg1', { foo: 'bar' }
) to
'arg1', { hash1: 'foo', hash2: 'bar', hash3: 'baz' }
which is, by definition, merging (note how the surrounding structure—the hash—is still there). Whereas de-structuring goes from
'arg1', [1, 2, 3]
to
'arg1', 1, 2, 3
Solution 2
It's 2018 and this deserves an update. Ruby 2.0 introduced keyword arguments and with that also the hash splat operator **
. Now you can simply do the following:
def foo(arg1, opts)
[arg1, opts]
end
opts = {hash2: 'bar', hash3: 'baz'}
foo('arg1', hash1: 'foo', **opts)
#=> ["arg1", {:hash1=>"foo", :hash2=>"bar", :hash3=>"baz"}]
Solution 3
There is no such thing (although it has been proposed). Since this will change the parsing rule, it cannot be implemented within Ruby. The best I can think of is to define *
on hash like
class Hash; alias :* :merge end
and use it in one of the following ways:
foo('arg1', {hash1: 'foo'}*opts)
foo('arg1', {hash1: 'foo'} *opts)
foo('arg1', {hash1: 'foo'}. *opts)
the last of which I think is reasonably close to what you wanted.
Solution 4
If you're ok with using active_support:
require 'active_support/core_ext/hash/slice.rb'
def foo(*args)
puts "ARGS: #{args}"
end
opts = {hash2: 'bar', hash3: 'baz'}
foo *opts.slice(:hash2, :hash3).values
...or you could monkey-patch your own solution:
class Hash
def pluck(*keys)
keys.map {|k| self[k] }
end
end
def foo(*args)
puts "ARGS: #{args}"
end
opts = {hash2: 'bar', hash3: 'baz'}
foo *opts.pluck(:hash2, :hash3)
Drew
Updated on July 08, 2022Comments
-
Drew almost 2 years
You can destructure an array by using the splat operator.
def foo(arg1, arg2, arg3) #...Do Stuff... end array = ['arg2', 'arg3'] foo('arg1', *array)
But is there a way to destruct a hash for option type goodness?
def foo(arg1, opts) #...Do Stuff with an opts hash... end opts = {hash2: 'bar', hash3: 'baz'} foo('arg1', hash1: 'foo', *opts)
If not native ruby, has Rails added something like this?
Currently I'm doing roughly this with
foo('arg1', opts.merge(hash1: 'foo'))
-
Andrew Marshall over 11 yearsPerhaps using
+
instead of*
would make more sense? -
sawa over 11 years@AndrewMarshall I agree with that. I wanted to make it look close to the splat operator.
-
Andrew Marshall about 11 yearsWell, did this answer your question? Don’t forget to upvote/accept answers to your questions
:)
-
3limin4t0r about 5 yearsKeep in mind that this only works with symbol keys.
{**{a: 1}, **{b: 2}} #=> {:a=>1, :b=>2}
while{**{'a' => 1}, **{'b' => 2}} #=> TypeError (wrong argument type String (expected Symbol))
-
Brian about 4 yearsYep. I was looking to add some keys to a
model.as_json
call and used Rails'symbolize_keys
method:hash = {a: 1, b: 2, **model.as_json.symbolize_keys}
-
Tyler Rick over 3 yearsIt's now possible in Ruby 3, though with a different/new syntax:
{a: 1, b: 2, c: 3, d: 4} => {a:, b:, **rest}
(see ruby3.dev/ruby-3-fundamentals/2021/01/06/…)