Ruby 3 Keyword Arguments

Ruby introduced deprecation warnings in 2.71 in preparations for Ruby 3‘s incompatibility changes to keyword arguments.

Let‘s recap for what we have now.

What kind of arguments do we have in Ruby?

Positional arguments, optional arguments, and keyword arguments.

def positional_method(a); end
def optional_method(b = {}); end
def keyword_argument_method(c: nil, r:); end
def optional_splat(*d); end
def optional_double_splat(**e); end

a is an positional argument.
b is an optional argument, defaults to empty hash.
c is an keyword argument, defaults to nil.
r is a required keyword argument.
d is an optional splat argument.
e is an optional double splat argument.

Splat Operators

* is called splat, and ** is called double splat. You can use these operators in method signatures and method calls:

def a_method(*args); end
def another_method(**args); end
In method signatures

Single splat packs the argument into Array.
Double splat packs the argument into Hash.

In method calls

Single splat unpacks the argument from Array.
Double splat unpacks the argument from Hash.

Keyword Arguments before Ruby 2

Before Ruby 2, we use options Hash to achieve keyword arguments, and Hash#fetch for required keyword arguments:

def ruby_19_translate(options = {})
  puts options.fetch(:key, "en")
end

def ruby_19_required_translate(options = {})
  puts options.fetch(:key)
end

define_method(:ruby_19_block_translate) do |options = {}|
  puts options.fetch(:key, "en")
end

define_method(:ruby_19_block_required_translate) do |options = {}|
  puts options.fetch(:key)
end

ruby_19_translate # => "en"
ruby_19_required_translate # KeyError: key not found: :key
ruby_19_block_translate # => "en"
ruby_19_block_translate(key: "es") # => "es"
ruby_19_block_required_translate # KeyError: key not found: :key
ruby_19_block_required_translate(key: "es") # => "es"

Keyword Arguments before Ruby 2.7

Keyword arguments first introduced in Ruby 2.0. Block keyword arguments also introduced:

def translate(key: "en")
  puts key
end

def required_translate(key:)
  puts key
end

define_method(:block_translate) do |key: 'en'|
  puts key
end

define_method(:block_required_translate) do |key:|
  puts key
end

translate # => "en"
required_translate # ArgumentError: missing keyword: :key
required_translate(key: "es") # => "es"
block_translate # => "en"
block_translate(key: "es") # => "es"
block_required_translate # ArgumentError: missing keyword: :key
block_required_translate(key: "es") # => "es"

Required keyword argument added in Ruby 2.1.

Automatic Conversion before Ruby 2.7

If the last argument of a method call is a Hash, Ruby < 2.7 will automatically convert to Keyword Arguments.

What deprecated in Ruby 2.7 and will break in Ruby 3?

The abovementioned automatic conversion will stop. Emit a warning in Ruby 2.7, which will eventually break your app in Ruby 3.

What should we do before Ruby 3?

Upgrade to Ruby 2.7 and fix the places rely on such automatic conversion.

Be explicit.

Wrap argument in {} if you want to pass in a hash:

translate({ key: value })

# instead of

translate(key: value)

Prefix argument with ** if you want to pass in keywords:

keywords = { throw: true, raise: false }

translate(**keywords)

# instead of

translate(keywords)

Use double splat keyword arguments instead of options hash:

def good(**kwargs)
  kwargs
end

# instead of

def old(kwargs = {})
  kwargs
end

If you can‘t convert, use ruby2_keywords to keep the old behavior first.

The migration path

Related


Hope this helps! Thanks for reading.

  • 1

    It seems version 2.7 is a wall for programming language progression (Python 2.7 -> 3.0).