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}]
.