Posted over 5 years ago. Visible to the public. Linked content.

Heads up: Ruby implicitly converts a hash to keyword arguments

When a method has keyword arguments, Ruby offers implicit conversion of a Hash argument into keyword arguments. This conversion is performed by calling to_hash on the last argument to that method, 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 simple Hash 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

Copy
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.

Does your version of Ruby on Rails still receive security updates?
Rails LTS provides security patches for old versions of Ruby on Rails (3.2 and 2.3).

Owner of this card:

Avatar
Dominik Schöler
Last edit:
over 3 years ago
by Henning Koch
About this deck:
We are makandra and do test-driven, agile Ruby on Rails software development.
License for source code
Posted by Dominik Schöler to makandra dev
This website uses short-lived cookies to improve usability.
Accept or learn more