How to Mix Required Argument and Optional Arguments in Ruby

Mixing keyword with regular arguments in Ruby?

A pseudo-regex for parameter lists in Ruby (this applies equally to methods, blocks and lambda literals) is something like this:

mand* opt* splat? mand* (mand_kw | opt_kw)* ksplat? block?

Here's an example:

def foo(m1, m2, o1=:o1, o2=:o2, *splat, m3, m4, 
ok1: :ok1, mk1:, mk2:, ok2: :ok2, **ksplat, &blk)
Hash[local_variables.map {|var| [var, eval(var.to_s)] }]
end

method(:foo).arity
# => -5

method(:foo).parameters
# => [[:req, :m1], [:req, :m2], [:opt, :o1], [:opt, :o2], [:rest, :splat],
# [:req, :m3], [:req, :m4], [:keyreq, :mk1], [:keyreq, :mk2],
# [:key, :ok1], [:key, :ok2], [:keyrest, :ksplat], [:block, :blk]]

foo(1, 2, 3, 4)
# ArgumentError: missing keywords: mk1, mk2

foo(1, 2, 3, mk1: 4, mk2: 5)
# ArgumentError: wrong number of arguments (3 for 4+)

foo(1, 2, 3, 4, mk1: 5, mk2: 6)
# => { m1: 1, m2: 2, o1: :o1, o2: :o2, splat: [], m3: 3, m4: 4,
# ok1: :ok1, mk1: 5, mk2: 6, ok2: :ok2, ksplat: {},
# blk: nil }

foo(1, 2, 3, 4, 5, mk1: 6, mk2: 7)
# => { m1: 1, m2: 2, o1: 3, o2: :o2, splat: [], m3: 4, m4: 5,
# ok1: :ok1, mk1: 6, mk2: 7, ok2: :ok2, ksplat: {},
# blk: nil }

foo(1, 2, 3, 4, 5, 6, mk1: 7, mk2: 8)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [], m3: 5, m4: 6,
# ok1: :ok1, mk1: 7, mk2: 8, ok2: :ok2, ksplat: {},
# blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, mk1: 8, mk2: 9)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5], m3: 6, m4: 7,
# ok1: :ok1, mk1: 8, mk2: 9, ok2: :ok2, ksplat: {},
# blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, 8, mk1: 9, mk2: 10)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8,
# ok1: :ok1, mk1: 9, mk2: 10, ok2: :ok2, ksplat: {},
# blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8,
# ok1: 9, mk1: 10, mk2: 11, ok2: :ok2, ksplat: {},
# blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8,
# ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {},
# blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, 8, ok1: 9, mk1: 10, mk2: 11, ok2: 12, k3: 13)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8,
# ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {k3: 13},
# blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, 8,
ok1: 9, mk1: 10, mk2: 11, ok2: 12, k3: 13, k4: 14)
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8,
# ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {k3: 13, k4: 14},
# blk: nil }

foo(1, 2, 3, 4, 5, 6, 7, 8,
ok1: 9, ok2: 10, mk1: 11, mk2: 12, k3: 13, k4: 14) do 15 end
# => { m1: 1, m2: 2, o1: 3, o2: 4, splat: [5, 6], m3: 7, m4: 8,
# ok1: 9, mk1: 10, mk2: 11, ok2: 12, ksplat: {k3: 13, k4: 14},
# blk: #<Proc:0xdeadbeefc00l42@(irb):15> }

[Note: mandatory keyword arguments will be introduced in Ruby 2.1, all the rest already works.]

Methods with optional and keyword arguments

If you want this exact signature, you should probably use double-splat for b parameter:

def test(a, c: 1, **b)
[a, b, c]
end
test 1, {hello: :world}
#⇒ [1, {hello: :word}, 1]

There are issues distinguishing named keyword parameters and the hash itself when the latter is passed immediately before named keywords.

Mixing keyword argument and arguments with default values duplicates the hash?

TL;DR ruby allows passing hash as a keyword argument as well as “expanded inplace hash.” Since change_hash(rand: :om) must be routed to keyword argument, so should change_hash({rand: :om}) and, hence, change_hash({}).


Since ruby allows default arguments in any position, the parser takes care of default arguments in the first place. That means, that the default arguments are greedy and the most amount of defaults will take a place.

On the other hand, since ruby lacks pattern-matching feature for function clauses, parsing the given argument to decide whether it should be passed as double-splat or not would lead to huge performance penalties. Since the call with an explicit keyword argument (change_hash(rand: :om)) should definitely pass :om to keyword argument, and we are allowed to pass an explicit hash {rand: :om} as a keyword argument, Ruby has nothing to do but to accept any hash as a keyword argument.


Ruby will split the single hash argument between hash and rand:

k = {"a" => 42, rand: 42}
def change_hash(h={}, rand: :om)
h[:foo] = 42
puts h.inspect
end
change_hash(k);
puts k.inspect

#⇒ {"a"=>42, :foo=>42}
#⇒ {"a"=>42, :rand=>42}

That split feature requires the argument being cloned before passing. That is why the original hash is not being modified.

Ruby Object Initialize with Optional Arguments

If you're going to do that you need to accept not an array of arguments (varargs style) like you've specified, but either an options hash or the new keyword-args style option.

The classic approach:

def initialize(attributes = nil)
attributes and attributes.each do |k,v|
instance_variable_set("@#{k}", v) unless v.nil?
end
end

The new keyword-arguments approach:

def initialize(**options)
options.each do |k,v|
instance_variable_set("@#{k}", v) unless v.nil?
end
end

You can also subclass OpenStruct to get behaviour like this for free.

Keep in mind there's a new option style that might be a good mix of both these approaches:

def initialize(sid: '123', token: '...', ...)
@sid = sid
@token = token
# ...
end

You can specify defaults for the named keyword arguments.

Ruby method with optional options and &block parameter

Yes, it is possible.

When mixing different kinds of parameters, they have to be included in the method definition in a specific order:

  1. Positional parameters (required and optional) and a single splat parameter, in any order;
  2. Keyword parameters (required and optional), in any order;
  3. Double splat parameter;
  4. Block parameter (prefixed with &);

The order above is somewhat flexible. We could define a method and begin the parameter list with a single splat argument, then a couple of optional positional arguments, and so on. Even though Ruby allows that, it's usually a very bad practice as the code would be hard to read and even harder to debug. It's usually best to use the following order:

  1. Required positional parameters;
  2. Optional positional parameters (with default values);
  3. Single splat parameter;
  4. Keyword parameters (required and optional, their order is irrelevant);
  5. Double splat parameter;
  6. Explicit block parameter (prefixed with &).

Example:

def meditate cushion, meditation="kinhin", *room_items, time: , posture: "kekkafuza", **periods, &b
puts "We are practicing #{meditation}, for #{time} minutes, in the #{posture} posture (ouch, my knees!)."
puts "Room items: #{room_items}"
puts "Periods: #{periods}"
b.call # Run the proc received through the &b parameter
end

meditate("zafu", "zazen", "zabuton", "incense", time: 40, period1: "morning", period2: "afternoon" ) { puts "Hello from inside the block" }

# Output:
We are practicing zazen, for 40 minutes, in the kekkafuza posture (ouch, my knees!).
Room items: ["zabuton", "incense"]
Periods: {:period1=>"morning", :period2=>"afternoon"}
Hello from inside the block

Notice that when calling the method, we have:

  • Provided the cushion mandatory positional argument;
  • Overwritten the default value of the meditation optional positional argument;
  • Passed a couple of extra positional arguments (zabuton and incense) through the *room_items parameter;
  • Provided the time mandatory keyword argument;
  • Omitted the posture optional keyword argument;
  • Passed a couple of extra keyword arguments (period1: "morning", period2: "afternoon") through the **periods parameter;
  • Passed the block { puts "Hello from inside the block" } through the &b parameter;

Please note the example above servers only to illustrate the possibility of mixing different types of parameters. Building a method like this in real code would be a bad practice. If a method needs that many arguments, it's probably best to split it into smaller methods. If it's absolutely necessary to pass that much data to a single method, we should probably create a class to store the data in a more organized way, then pass an instance of that class to the method as a single argument.

Dealing with 3 or more default arguments Ruby

Using keyword arguments?

def foo(a: 1, b: 2, c: 3)
puts [a, b, c]
end

foo(a: 5, c: 7)

Can I have required named parameters in Ruby 2.x?

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'

Ruby - Mixing named and positional parameters, why does order matter?

Keyword parameters and arguments are relatively new in Ruby. They were only introduced in Ruby 2.0.

Before Ruby had keyword parameters and arguments, there was a widely-used idiom of passing a Hash literal as the last argument of the method. This idiom looked something like this:

DEFAULTS = {
:mode => 'w',
:eol => :crlf,
}

def open_file(name, options = {})
raise ArgumentError unless options[:encoding]
options = DEFAULTS.merge(option)
mode, eol, encoding = options[:mode], options[:eol], options[:encoding]
# do your thing
end

open_file('test.txt', { :mode => 'r', :encoding => 'UTF-8' })

In order to make it look a bit more "keyword-like", you are allowed to leave out the parentheses if you pass a Hash literal as the very last argument of a message send:

open_file('test.txt', :mode => 'r', :encoding => 'UTF-8')

In Ruby 1.9, an alternative syntax for a limited subset of Hash literals was introduced: when the key is a Symbol that is also a valid Ruby identifier (e.g. :foo, but not :'foo-bar'), then you can write it like this:

{ foo: bar }

instead of

{ :foo => bar }

So, we could call our method from above like this:

open_file('test.txt', { mode: 'r', encoding: 'UTF-8' })

and, since the rule about leaving out parentheses still applies, also like this:

open_file('test.txt', mode: 'r', encoding: 'UTF-8')

This looks very much like keyword arguments in other languages. In fact, this alternative literal syntax for Hashes with Symbol keys was at least partially specifically designed to provide a transition path for introducing keyword parameters and arguments into Ruby.

In Ruby 2.0, optional keyword parameters with default keyword arguments were introduced:

def open_file(name, mode: 'w', eol: :crlf, encoding: nil)
raise ArgumentError unless encoding
# do your thing
end

Then in Ruby 2.1 mandatory keyword parameters and arguments:

def open_file(name, mode: 'w', eol: :crlf, encoding:)
# do your thing
end

As you probably know, calling this method looks exactly like it did before:

open_file('test.txt', mode: 'r', encoding: 'UTF-8')

Note, however, that you can no longer tell what this means! You cannot know whether mode: 'r', encoding: 'UTF-8' is a Hash literal or two keyword arguments (in other words, you don't even know whether this is one or two arguments!) without looking at the definition of the method you are calling.

It was decided that Ruby 2.0 should be maximally backwards and forwards compatible with Ruby 1.9.

Therefore, all of the following must be true:

  • A method that is defined with an options hash and is called with an options hash must still work.
  • A method that is defined with an options hash and is called with keyword arguments must still work.
  • A method that is defined with keyword parameters and is called with an options hash literal must still work.

In order to make all of this work, there are a lot of implicit conversions between hash literals and keyword arguments. Getting this to work without nasty corner cases is just much easier if keyword parameters and keyword arguments are only allowed to appear where the "fake" keyword syntax was allowed before, i.e. at the very end of the parameter list and argument list.

Actually, there are still a lot of nasty corner cases caused by this "blurring the lines" between hashes and keyword parameters. If you look through the Ruby issue tracker, you will find that a significant portion of issues reported since Ruby 2.0 are related to un-intuitive or simply buggy behavior in this regard. Every new release brings new changes, but one gets the feeling that for every hole they patch, they create two new ones.

Now, just imagine what it would be like if the rules were even less strict!


Here are some examples of those afore-mentioned issues:

  • Unexpect behavior when using keyword arguments
  • Can't pass hash to first positional argument; hash interpreted as keyword arguments
  • Keyword argument oddities
  • Splat with empty keyword args gives unexpected results
  • inconsistent behavior using ** vs hash as method parameter
  • Mixing kwargs with optional parameters changes way method parameters are parsed
  • Procs keyword arguments affect value of previous argument
  • Optional argument treated as kwarg
  • Object converted to Hash unexpectedly under certain method call
  • Default Parameters don't work
  • Some weird behaviour with keyword arguments
  • Keyword arguments are ripped from the middle of hash if argument have default value
  • must not optimize foo(**{}) out
  • non-symbol keyword in double splat, such as **{2 => 3}, raises TypeError or not

How to make an optional strong parameters key but filter nested params?

What about:

params.require(:item).permit! if params[:item]

You cannot require an optional parameter. That is contradictory.

Edit: as mtjhax mentioned in his comment, there is advice from here to use fetch instead: params.fetch(:item, {}).permit!



Related Topics



Leave a reply



Submit