Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Handling a method with an optional argument followed by a list of keyword arguments

Tags:

I want to pass an optional argument (any datatype, even a Hash) followed by a list of keyword arguments (which can be empty) to a method.

This is what I got:

def my_method(subject = nil, **options)
  [subject, options]
end

The method output is as expected in the following cases:

  • nothing

    my_method()
    # => [nil, {}]
    
  • a subject

    my_method('foo')
    # => ["foo", {}]
    
  • a literal subject with options

    my_method('foo', bar: 'bar')
    # => ["foo", {:bar=>"bar"}]
    
  • a Hash as subject with options

    my_method({foo: 'foo'}, bar: 'bar')
    # => [{:foo=>"foo"}, {:bar=>"bar"}]
    
  • no subject, only options

    my_method(bar: 'bar')
    # => [nil, {:bar=>"bar"}]
    

When passing a Hash as subject and no options, the desired outcome is:

my_method({foo: 'foo'})
# => [{:foo=>"foo"}, {}]

But I get the following; I don't get the correct subject:

my_method({foo: 'foo'})
# => [nil, {:foo=>"foo"}]

Is my_method(foo: 'foo') equivalent to my_method({foo: 'foo'})? Any ideas on how I could get the desired outcome?

like image 426
Taschetto Avatar asked Jan 10 '19 13:01

Taschetto


2 Answers

See, you have **options as an argument which do not have any default value & first argument have default value. So understand following single argument case,

Whenever single argument is passed it is tried to assign to second argument (as first one is holding default nil) & if it fails due to type mismatch then it assign to first argument. That's how my_method(4) works.

Now Suppose you have single argument passed as hash, which match to assign to 2nd argument then of course it get assigned to second argument & first is set default nil.

If you want to make it work, then you can do following,

> my_method({sd: 4}, {})
 => [{:sd=>4}, {}]

Or you can provide argument name while passing,

> my_method(subject: {sd: 4})
 => [{:sd=>4}, {}]
like image 108
ray Avatar answered Oct 22 '22 12:10

ray


As far as I know this is a limitation of how Ruby passes arguments. It can't tell the difference between my_method({foo: 'foo'}) and my_method(foo: 'foo')

Why not hack around it with this?

if subject.is_a?(Hash) && options.empty?
  subject, options = nil, subject
end

This assumes that subject shouldn't be a hash though.

like image 26
Max Avatar answered Oct 22 '22 12:10

Max