Kris
Kris

Reputation: 19948

How to pass optional named arguments in Ruby 2.0

Is there a way for an argument to be truly optional so I know if it was set by caller or not?

Having an argument use nil as the default does not work because there is no way to know if the caller passed nil or it is the default value for the argument.

Before named arguments, in Ruby 1.9, using an options hash:

def foo(options = {})
  # …
  bar(options)
end

def bar(options = {})      
  puts options.fetch(:name, ‘unknown’) # => 'unknown'
end

With Ruby 2.0 named arguments:

def foo(name: nil)
  # …
  bar(name: name)
end

def bar(name: ‘unknown’)
  # …
  puts name # => nil, since nil is explicitly passed from `foo`
end

Upvotes: 16

Views: 14864

Answers (4)

Kris
Kris

Reputation: 19948

You can use an "undefined" value as the default to tell it apart from a nil:

Undefined = Object.new.freeze

def foo(a: Undefined)
  puts a
end

foo(a: nil) # => "nil"
foo # => "#<Object:0x000056004b29a058>"

If you use dry-rb, then you can use Dry::Core::Constants::Undefined (source), which provides a few extras:

Undefined = Dry::Core::Constants::Undefined

def foo(a: Undefined)
  puts a
end

foo(a: nil) # => "nil"
foo # => "Undefined"

Upvotes: 2

Marc-Andr&#233; Lafortune
Marc-Andr&#233; Lafortune

Reputation: 79562

I would assume from you example that you are not using name in foo but want to pass it along.

You should use the catch all for named arguments instead:

def foo(**named)
  # …
  bar(**named)
end

def bar(name: 'unknown')
  # …
  puts name
end

foo # => 'unknown'
foo(name: 'baz') # => 'baz'

Note that this not interfere with other arguments (named or not), so you if you had other arguments for foo:

def foo(what, other: 42, **named)

Upvotes: 4

Alex Peachey
Alex Peachey

Reputation: 4686

It's not entirely clear why you want to know if the argument was provided or not. The most useful reason to know, is because you want to require one of your optional arguments. If that's the reason, then starting with Ruby 2.1, you can get what you want.

def foo(a:,b:nil,c:3)
 [a, b, c]
end

foo a: 1, b: 2, c: 3
#=> [1, 2, 3]
foo a: 1, b: 2
#=> [1, 2, 3]
foo a: 1
#=> [1, nil, 3]
foo
#ArgumentError: missing keyword: a

Upvotes: 2

BroiSatse
BroiSatse

Reputation: 44685

Probably the best way to go would be:

def bar(name: nil)
  name ||= 'unknown'
  puts name
end

Upvotes: 22

Related Questions