3

I am using the percent string formatting like so:

'fetching imap (%<host>s/%<user>s port=%<port>s...' % options

This works fine when the options hash has symbol keys { host: 'example.com' }, but raises a KeyError when it has string keys { 'host' => 'example.com' }:

KeyError: key<host> not found

This is problematic because Rails’ HashWithIndifferentAccess uses string keys by default. If using ActiveSupport, one solution is to call symbolize_keys on the hash.

Is there an alternative way to make the String#% method try string keys as well as symbol keys?

Is this a bug in Ruby that deserves a bug report? Or are there reasons for enforcing this behavior, like the edge case where both :host and 'host' keys exist?

sawa
  • 165,429
  • 45
  • 277
  • 381
Ryan Lue
  • 916
  • 10
  • 29
  • Why would this be a bug? I don't understand your thoughts. At most, it could be a feature request (although I would be strongly against it). – sawa Aug 17 '18 at 06:47

2 Answers2

2

It's not a bug that Ruby doesn't treat strings and symbols interchangeably; as you note, you're using Rails' HashWithIndifferentAccess, which exists exactly because Ruby regularly considers strings and symbols unrelated.

This is a similar situation to keyword arguments, which also do not allow string-keyed hashes.

At best, you could try to argue that sprintf should use the ruby-level accessors methods ([] or fetch), which HashWithIndifferentAccess overrides, instead of directly doing the C-level rb_hash_lookup2.

Alternatively, Rails might be able to make this work by partly restoring the default behaviour that existed between c918a3a0691 and a218a35e660.

matthewd
  • 4,332
  • 15
  • 21
0

I am using the percent string formatting … This works fine when the options hash has symbol keys … but raises a KeyError when it has string keys

If using ActiveSupport [from Rails], one solution is to call symbolize_keys on the hash.

For a solution without Rails you can use:

options = options.transform_keys(&:to_sym)

or

options.transform_keys!(&:to_sym)

See the manual. This requires Ruby 2.5.0 or higher, though. For more options, also for earlier Ruby versions, see this related question.

tanius
  • 14,003
  • 3
  • 51
  • 63