One of the nicest bits of syntax sugar in Ruby are keywords (** or double-splat).
Keywords in Method Parameters
Just like the splat operator (*args) captures any parameters as an Array of
arguments, the double-splat operator (**attrs) captures any option parameters
as a Hash.
The nice thing is that it allows to destructure a Hash argument into any of
its individual keys, and mark it as required, or make it optional and provide a
default value for it.
def greet(first_name:, last_name: nil, **attrs)
name = [first_name, attrs[:middle_name], last_name].compact.join(' ')
puts "Hi #{ name }!"
end
greet(first_name: 'Gale')
# Hi Gale!
greet(last_name: 'John')
# ArgumentError (missing keyword: first_name)
greet(first_name: 'Bruce', middle_name: 'Wayne', last_name: 'Keaton')
# Hi Bruce Wayne Keaton!There's one big gotcha which comes up a lot when first learning Ruby:
def greet(**attrs)
puts "Hi #{ attrs[:name] }!"
end
greet
# Hi !
greet('name' => 'Jane')
# ArgumentError (wrong number of arguments (given 1, expected 0))Many developers have scratched their heads wondering what's going on, until they learn the cause of this unintuitive message.
Only
Symbolkeys are allowed in keyword arguments 😲
Ruby handles keyword arguments differently in its internals, which unfortunately leaks into this error message.
Merging Hashes with the Double-Splat operator
The double-splat operator can also be used to combine hashes. The order matters, when resolving duplicate keys, the rightmost ones will take priority.
jane = { :first_name => "Jane", :last_name => "Doe" }
{ **jane, :last_name => "Johnson" }
# { :first_name => "Jane", :last_name => "Johnson" }
{ :last_name => "Johnson", **jane }
# { :last_name => "Doe, :first_name => "Jane" }There are a few caveats when combining hashes:
- It does not combine keys of different types, such as
StringandSymbol. - It can only be used with Hashes where all keys are
Symbols.
jane = { :first_name => "Jane", :last_name => "Doe" }
{ **jane, "first_name" => "John" }
# { :first_name => "Jane", :last_name=>"Doe", "first_name"=>"John" }
jane = { "first_name" => "Jane", "last_name" => "Doe" }
doe = { **jane, first_name: "John" }
# TypeError (hash key "first_name" is not a Symbol)This message used to be very cryptic like the one for keyword arguments, but it has improved a lot since the feature was first added 🎉
A quick note about Rails
When using Rails a developer might think:
I can obtain values from
paramsusingSymbolkeys, but I can't pass them as keyword arguments! Thought they were all Symbol keys?
The short answer is no. In Rails controllers, params is an instance of HashWithIndiferentAccess.
You may index it with Symbol keys but internally it uses String keys.
Fortunately, symbolize_keys is our friend here, allowing us to transform any
Hash, such as HTTP parameters, to something we can pass as keyword arguments.
Summary
So there you have it, ** is to Hash what * is to Array, and is a very
convenient tool to write shorter, expressive code.
Just watch out for errors when using non-Symbol keys in keyword arguments,
at least until Ruby improves that error message 😉


