3

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?

Deltik
  • 1,129
  • 7
  • 32
  • 2
    This would make for a very confusing API. What's the motivation behind passing a Hash in as a positional argument and having hash-style options as well? Why can't you declare it with specific args, like `example(arg = nil, option: false, other: true)`? – tadman Jul 10 '17 at 17:13
  • @tadman: I was hoping the first positional argument could be a Hash of string values to be converted to JSON as a payload so that the client code wouldn't have to call `.to_json`. The named arguments would be options that go into [`RestClient::Resource`](http://www.rubydoc.info/gems/rest-client/RestClient/Resource)'s options, which can take a lot of different options, so I felt it would be simpler to take in all possibilities rather than pick and choose many of them to pass on. – Deltik Jul 10 '17 at 17:21
  • If You pass string key instead of symbol key It will show in *args. Kwargs work only with symbol keys. – Rada Bogdan Jul 10 '17 at 17:46
  • @RadaBogdan: Run `example({"having spaces": "works?"})` in `irb`, and you'll see that `**kwargs` still eats up the argument. – Deltik Jul 10 '17 at 17:51

1 Answers1

2

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
tadman
  • 208,517
  • 23
  • 234
  • 262