Marvin
Marvin

Reputation: 3025

Why does Ruby string formatting with hashes behave inconsistently across versions?

I wrote some code that used a "dynamic hash" to return values for keys, where the values were calculated. I tested it under irb (RUBY_VERSION 2.3.3) and everything seemed good. Below is a trivial example demonstrating the idea.

PROPS = Hash.new { |hash,key| key.to_s + "!" }
"Foo: %{foo} Bar: %{bar}" % PROPS  # => "Foo: foo! Bar: bar!"
PROPS[:xyzzy] # => "xyzzy!"

But then deploying my code into the environment where it used (a plugin for the modeling tool Sketchup) which apparently has Ruby 2.2.4 the string formatting example above yields a KeyError: key{foo} not found.

PROPS = Hash.new { |hash,key| key.to_s + "!" }
"Foo: %{foo} Bar: %{bar}" % PROPS  # KeyError: key{foo} not found
PROPS[:xyzzy] # => "xyzzy!"

But accessing the hash with any key works fine... Reading at http://ruby-doc.org/core-2.2.4/Kernel.html#method-i-sprintf doesn't provided much in the way of specifying why hash defaults would not behave as expected.

Obviously I can do different things, like invent my own replacement functions and variable syntax. As an aside, apparently the "hashes" passed to "%" or sprintf must actually BE Hash objects, violating Ruby's supposed duck-typing flexibility.

Upvotes: 3

Views: 1046

Answers (1)

Felix
Felix

Reputation: 4716

I can confirm that it does not work in Ruby 2.1.5 .

I will give some hints on how you could find out which ruby code to define to get the stuff going without reading C or Ruby code from MRI.

I know, this is not a full answer, but the text is too long to give in a comment.

Following is an irb session

  >> detector = Object.new
  => #<Object:0x00000002257900>
  >> def detector.method_missing m
  >>   puts m.to_s
  >> end
  => :method_missing
  >> "Foo: %{fnoo} Bar: %{bar}" % detector
  to_ary
  to_hash
  ArgumentError: one hash required
      from (irb):37:in `%'
      from (irb):37
      from /home/felix/.rvm/rubies/ruby-2.1.5/bin/irb:11:in `<main>'

This tells us that during interpolation methods were called that are not implemented by our dummy "detector" Object (to_hash to be precisely; through other tests I know that to_ary is also called if given object is a Hash, so we can ignore that one).

It does however not tell us whether already something like detector.class or detector is_a Hash? etcpp. were called.

Now I await the downvotes ;)

Btw, if you want to dive in via C - and I came to believe that this is probably needed in this case - you can start digging here: https://github.com/ruby/ruby/blob/6d728bdae9de565ad9d0b2fee2d4c2a33c6f4eac/sprintf.c#L579 (more or less "sprintf" on ruby 2.1).

Upvotes: 2

Related Questions