Reputation: 19948
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
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
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
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
Reputation: 44685
Probably the best way to go would be:
def bar(name: nil)
name ||= 'unknown'
puts name
end
Upvotes: 22