Ruby adds support for forwarding arguments to a method, along with the leading arguments


Ruby 2.7 has added a new shorthand syntax ... for forwarding arguments to a method. Here’s a short read on forwarding arguments for a quick refresher.

Before

... was limited to forwarding arguments to a method, as shown below:


def travel(...)
  by_road(...)
end

However there could be use cases where ... can be used along with the leading arguments as shown below:

def travel(preference, ...)
  if preference == "air"
    by_flight(...)
  else
    by_road(...)
  end
end

After

Now, Ruby 3.0 has added support for the forwarding arguments along with the leading arguments.

Let’s understand this with the help of an example:

def transform(a, ...)
  process(a, ...)
end

def process(a, *args, **kwargs, &block)
  if block
    block.call(args, kwargs)
  else
    [a, args, kwargs]
  end
end

In the above example, the method transform is declared with a positional argument a along with ...

It calls the method process with the same arguments, whose signature includes the positional argument a along with *args, **kwargs, &block.

The idea here is, ... will assign additional arguments to *args, keyword arguments to **kwargs and block to &block respectively.

Let’s experiment with the above code by passing some values to the method transform:

Example with the only positional argument:

  transform(1) # => [1, [], {}]

In the above example, argument a in the method transform got 1 as value, and it was passed to the method process.

Since we have not passed a block, else part in the method process gets executed, and a’s value 1 is substituted in [a, args, kwargs].

Likewise, we have not passed any args(additional arguments) or kwargs(keyword arguments).

The default values for args and kwargs are [] and {} respectively.

Hence the output is [1, [], {}].

Example with positional and additional arguments:

  transform(1, 2, 3) # => [1, [2, 3], {}]

In this example, a is the positional argument, so 1 is assigned to a. 2, 3 are assigned to ...

Since 2, 3 fall under the additional arguments, they will be assigned to *args in the method process.

Hence the output is [1, [2,3], {}].

Example with positional, additional and keyword arguments:

transform(1, 2, 3, a:1, b: 2) # => [1, [2, 3], {:a=>1, :b=>2}]

Here, 1 is assigned to a, [2, 3] is assigned to args, and {:a=>1, :b=>2} is assigned to kwargs.

Hence, the output is [1, [2, 3], {:a=>1, :b=>2}].

Example with block:

transform(1, 2, 3, a:1, b: 2) { |args, kwargs|  [args, kwargs]  }
 # =>  [[2, 3], {:a=>1, :b=>2}]

Here, since a block is passed, if block in the method process gets executed.

It calls the given block with additional arguments and keyword arguments, and the block is returning an array with the arguments passed to it.

Hence, the output is [[2, 3], {:a=>1, :b=>2}].