TLDR: keyword arguments only with with symbol-keyed hashes but DON'T symbolize keys on params unless you know what you're doing.
Ruby's keyword arguments only work with Hash
objects where all of the keys are symbols.
They do not with:
ActionController::Parameters
ActiveSupport::HashWithIndifferentAccess
HashWithIndifferentAccess
(as well as its decendant Parameters
) stores its keys as strings and will throw an error if passed into a method expecting keyword arguments.
RUBY_VERSION #=> "2.1.2"
require 'action_controller'
require 'active_support/core_ext'
normal_hash = { a: 'potato', name: 'Stanley Pickles' }
indiff_hash = normal_hash.with_indifferent_access
params_hash = ActionController::Parameters.new(normal_hash)
def foo(a: '1', b: '2', **args)
[a, b, args]
end
foo #=> ["1", "2", {}]
foo(nil) # ArgumentError: wrong number of arguments (1 for 0)
foo({}) #=> ["1", "2", {}]
foo(normal_hash) #=> ["potato", "2", {:name=>"Stanley Pickles"}]
foo(indiff_hash) # ArgumentError: wrong number of arguments (1 for 0)
foo(params_hash) # ArgumentError: wrong number of arguments (1 for 0)
A workaround is just to call #symolize_keys
from ActiveSupport's Core Extension.
This should never be called on request params
in Rails, however, since it opens a vulnerability to DoS attacks. A malicious user can potentially pass parameters with millions of different keys which stay in memory since symbols in Ruby pre-2.2 are never garbage collected.
The solution is to create a new Hash
or hash-like object with only the specific keys you need using #slice
(ActiveSupport Core Extension), #permit
(ActionController), #keep_if
(Ruby Core), etc. and then call #symbolize_keys
on the resulting object.