When a method has keyword arguments, Ruby offers
implicit conversion
Show archive.org snapshot
of a Hash
argument into keyword arguments. This conversion is performed by
calling to_hash
on the last argument to that method
Show archive.org snapshot
, before assigning optional arguments. If to_hash
returns an instance of Hash
, the hash is taken as keyword arguments to that method.
Issue
If you have ...
- an object that defines
to_hash
(may well be a simpleHash
instance) and - pass it to a method with optional arguments and keyword arguments
... then it is not set as the first optional argument. Instead, Ruby calls to_hash
on the object and tries to match the result to keyword arguments. If the hash contains unsupported keys (which it most likely will), Ruby will raise ArgumentError
. Bah. (If to_hash
does not return a Hash, assignment will work as expected – but this should actually never happen.)
I consider this overeager of Ruby. I will call to_hash
myself, or simply use the **double splat operator, if I need to.
Update: This is not a bug, it's a misunderstood feature of Ruby. There are pairs of conversion methods like to_h
and to_hash
, of which the former is for explicit conversion and the latter for implicit conversion. See this card for details.
Example
def method(arg = 'arg', kw_arg: 'kw_arg')
[arg, kw_arg]
end
# As expected:
method() # => ['arg', 'kw_arg']
method(kw_arg: 'your keyword') # => ['arg', 'your keyword']
# Intended as nicety: implicit hash conversion
method({kw_arg: 'hash kw_arg'}) # => ['arg', 'hash kw_arg']
# But has bad side effects:
o = String.new('example object')
def o.to_hash # Now o responds to #to_hash
{ kw_arg: 'o.to_hash' }
end
method(o)
# => ['arg', 'o.to_hash']
# Ruby thinks that o is a Hash and converts it to keyword arguments -.-
method(o, o)
# => ['example object', 'o.to_hash']
# Same here, but since only the *last* argument is converted,
# the first is properly assigned to the first optional argument
Workaround
Just don't mix optional and keyword arguments.
Update: Actually, do not define to_hash
when you need it for explicit conversion to a Hash. Define to_h
instead.