48

Ruby 2.0 is adding named parameters, like this:

def say(greeting: 'hi')
  puts greeting
end

say                     # => puts 'hi'
say(greeting: 'howdy')  # => puts 'howdy'

How can I use named parameters without giving a default value, so that they are required?

Marc-André Lafortune
  • 78,216
  • 16
  • 166
  • 166
Nathan Long
  • 122,748
  • 97
  • 336
  • 451

5 Answers5

92

There is no specific way in Ruby 2.0.0, but you can do it Ruby 2.1.0, with syntax like def foo(a:, b:) ...

In Ruby 2.0.x, you can enforce it by placing any expression raising an exception, e.g.:

def say(greeting: raise "greeting is required")
  # ...
end

If you plan on doing this a lot (and can't use Ruby 2.1+), you could use a helper method like:

def required
  method = caller_locations(1,1)[0].label
  raise ArgumentError,
    "A required keyword argument was not specified when calling '#{method}'"
end

def say(greeting: required)
  # ...
end

say # => A required keyword argument was not specified when calling 'say'
Marc-André Lafortune
  • 78,216
  • 16
  • 166
  • 166
19

At the current moment (Ruby 2.0.0-preview1) you could use the following method signature:

def say(greeting: greeting_to_say)
  puts greeting
end

The greeting_to_say is just a placeholder which won't be evaluated if you supply an argument to the named parameter. If you do not pass it in (calling just say()), ruby will raise the error:

NameError: undefined local variable or method `greeting_to_say' for (your scope)

However, that variable is not bound to anything, and as far as I can tell, cannot be referenced from inside of your method. You would still use greeting as the local variable to reference what was passed in for the named parameter.

If you were actually going to do this, I would recommend using def say(greeting: greeting) so that the error message would reference the name you are giving to your parameter. I only chose different ones in the example above to illustrate what ruby will use in the error message you get for not supplying an argument to the required named parameter.

Tangentially, if you call say('hi') ruby will raise ArgumentError: wrong number of arguments (1 for 0) which I think is a little confusing, but it's only preview1.

Nathan Long
  • 122,748
  • 97
  • 336
  • 451
Adam
  • 821
  • 4
  • 10
  • 4
    Now that I see it, it's obvious. And it is totally unrelated to Ruby 2.0 or named arguments, it works just the same way with "normal" optional arguments and always has: `def m(a = b) end; m # NameError: undefined local variable or method 'b' for main:Object`. – Jörg W Mittag Nov 06 '12 at 12:21
  • 1
    @Adam, I find your answer a bit misleading. The reason you get these errors is that you can have arbitrary Ruby code as the default value of an argument. This applies to regular positional args, too and has nothing to do with required keyword args. Try to put `Time.now` as a default value and see what happens. [Mark-Andre's answer](http://stackoverflow.com/a/15078852/75715) is more to the point here. – Dimitar Oct 14 '13 at 14:39
  • @Dimitar I thought the mechanism was clear, and that it was a nice hack around a missing feature which still produced a useful error. But I noticed the 2.1.0-preview1 release notes and Marc-Andre's answer will indeed be the official way, so I've changed the accepted answer (sorry, @Adam). – Nathan Long Oct 22 '13 at 13:52
  • Thanks so much for the "tangentially" part. I was definining my method to use a named parameter for the second one, but actually calling it in the ordinal style, and was getting the message "wrong number of arguments (2 for 1)" when there were very clearly two parameters and I had specified both. Because the output is confusing, it's hard to know how to look for help on that! – Tyler Collier Apr 30 '14 at 05:06
12

Combining the solutions from @awendt and @Adam,

def say(greeting: ->{ raise ArgumentError.new("greeting is required") }.call)
  puts greeting
end

You can DRY this up with something like:

def required(arg)
  raise ArgumentError.new("required #{arg}")
end

def say(greeting: required('greeting'))
  puts greeting
end

And combining that with @Marc-Andre's solution: https://gist.github.com/rdp/5390849

rogerdpack
  • 62,887
  • 36
  • 269
  • 388
Ben Taitelbaum
  • 7,343
  • 3
  • 25
  • 45
6

In Ruby 2.3, I can do

def say(greeting:)
  puts greeting
end

Then use it with...

say(greeting: "hello there")
thedanotto
  • 6,895
  • 5
  • 45
  • 43
  • This should be marked as the correct answer because of the newer versions of Ruby allow this. You get ArgumentError for free. – fmquaglia Feb 20 '19 at 17:41
3

How about:

def say(greeting: nil)
  greeting or raise ArgumentError
  puts greeting
end

say                     # => raises ArgumentError
say(greeting: 'howdy')  # => puts 'howdy'

Other than that, it is going to be difficult. According to this site, keyword arguments “are named parameters that have default values.“

Gabe Kopley
  • 16,281
  • 5
  • 47
  • 60
awendt
  • 13,195
  • 5
  • 48
  • 66