How to map hash items to method parameters?
Asked Answered
A

2

5

I have a method with a lengthy list of optional arguments, such as:

def foo(foo = nil, bar = nil, baz = nil, qux = nil)
    # no-op
end

I thought that calling the method and passing a split hash as a parameter would map the hash items to parameters by matching the key with the method parameter:

params = { bar: 'bar', foo: 'foo' }
foo(*params)

Unfortunately, when I examine the local variables after calling the method with a split hash, I get exactly what I'd expect if I passed in a split array, but it's not what I was hoping for:

foo == [:bar, 'bar'] # hoped: foo == 'foo'
bar == [:foo, 'foo'] # hoped: bar == 'bar'

What am I lacking here?

Arthro answered 26/8, 2012 at 10:52 Comment(0)
O
8

(this answer refers to the version of Ruby that was current when the question was asked. See edit for the situation today.)

Ruby does not support passing arguments by name. The splat operator (*) expands an arbitrary enumerable by calling to_ary on it and splices the result into the argument list. In your case, the enumerable you pass in is a hash, which gets transformed into an array of key-value pairs:

[2] pry(main)> params.to_a
=> [[:bar, "bar"], [:foo, "foo"]]

So the first two arguments of the function will be the values [:bar, "bar"] and [:foo, "foo"] (regardless of their parameter name!).

If you want to have something similar to keyword arguments in Ruby, you can make use of the fact that you don't need the braces when passing a hash to a function as the last argument:

def foo(opts = {})
    bar = opts[:bar] || <default>
    foo = opts[:foo] || <default>
    # or with a lot of parameters:
    opts = { :bar => <default>, :foo => <default>, ... }.merge(opts)
end

foo(foo: 3)  # equivalent to foo({ foo: 3 })

EDIT:

As of version 2.0, Ruby now supports named arguments with a dedicated syntax. Thanks to user jgoyon for pointing that out.

Outbreak answered 26/8, 2012 at 10:58 Comment(0)
F
2

This problem well known as named parameters. Because Ruby doesn't have named parameters so here's classical way to deal with it:

def foo(options = {})
  options = {:foo => nil, :bar => nil, :baz => nil, qux => nil}.merge(options)
  ..
end

or using ActiveSupport::CoreExtensions::Hash::ReverseMerge from Rails

def foo(options = {})
  options.reverse_merge!{:foo => nil, :bar => nil, :baz => nil, qux => nil}
  ..
end

In future version Ruby 2.0 will possible to use named parameters

def foo(foo: nil, bar: nil, baz: nil, qux: nil)
  puts "foo is #{foo}, bar is #{bar}, baz is #{baz}, ..."
  ..
end
Fortieth answered 26/8, 2012 at 11:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.