In Ruby 2.4.1, I have a method like this:
def example(*args, **kwargs)
p args
p kwargs
end
I can pass in positional arguments that are not Hash just fine:
irb(main):001:0> example("Greetings")
["Greetings"]
{}
And if I want to use named parameters, that's fine, too:
irb(main):002:0> example(something: 42)
[]
{:something=>42}
But trying to pass a Hash as a positional argument, this happens:
irb(main):002:0> example({something: 42})
[]
{:something=>42}
I want *args
to take {something: 42}
, not **kwargs
.
The positional arguments have to be optional, but even if *args
were arg=nil
, the double-splat is still too greedy:
irb(main):001:0> def example(arg=nil, **kwargs)
irb(main):002:1> p arg
irb(main):003:1> p kwargs
irb(main):004:1> end
=> :example
irb(main):005:0> example({"key":"value"})
nil
{:key=>"value"}
irb(main):006:0> example({"key":"value"}, this_is: "in kwargs")
{:key=>"value"}
{:this_is=>"in kwargs"}
How can I pass a Hash as a positional argument when the method takes **
as well?
That's always tricky to manage because the **kwargs
here will aggressively take ownership of that hash. This also presents some confusion on the part of the caller since they'll have to be careful when calling it regardless of what you do:
# Is this options or the body? It's not clear.
example({ payload: true })
I'd recommend switching to something more explicit, like:
def example(**kwargs)
body = kwargs.delete(:body)
end
Then calling it like this:
example(body: { ... }, user: "username", ...)
If you can spell out the allowed arguments, even better:
def example(body: nil, user: nil, password: nil)
# ...
end