Back

String Interpolation in Ruby

In my recent gem squasher I was faced with a problem of inserting dynamic data into output. For example:

1
"There are no migrations prior %inputed date%"

Of course, at this moment every reader could say – “what? it’s easy enough. Just the one line of code”

1
puts "There are no migrations prior #{ date }"

But I decided to hold all messages into external json file. To generate some message code triggers Squasher#tell method with a key. It cleans code from longs lines of messages, allows easialy add color into messages and holds all text in the one place. View & system logic should always be splitted, even if there are a few messages. After quick search I found a working solution. A json file should contain messages in the next format:

1
"There are no migrations prior #{ opts[:date] }"

To evaluate this message with actual data, you need to do a next hack:

1
message = eval(%Q|"#{ json_message }"|)

Once again, this code does it’s job. But a solution was not good. I continued to searching for a better one. As you know out-of-box ruby offers a render engine called ERB. My problem was solved with next code:

1
2
3
require 'erb'
json_message = "There are no migrations prior <%= opt[:date] %>"
message = ERB.new(json_message).result(binding)

Using ERB makes message processing better, but still has few downsides. First of all, json messages have knowlandge that they will be evaluted in ruby env with a ruby hash opts. Second, this code adds extra dependency of erb and it’s usage is not the best expirience.

I felt that this problem could be solved in a more elegant way. I recalled that I18n has the same problem. And it offers the easy interface to insert some custom data in the next format:

1
"There are no migrations prior %{date}"

After opening source code(thanks, budler, for ability to do it) I was even more surpriced that you can do it in Ruby out-of-box via String#% method. If you don’t know ruby strings have % method which allows format & insert variables into string. For example:

1
2
$ "%d - %f" % [10.0, 10.0] =>
# "10 - 10.000000"

But if argument will be a hash instead of array, you could inject a value by it’s key:

1
"There are no migrations prior %{date}" % { :date => "2014/02/16" }

You can also use "%(date)" which produces the same string. The difference will be if a hash doesn’t have mentioned key.

  • "%(date)" will raise ArgumentError with “malformed format string – %(”
  • "%{date}", on my opinion, raises more informative KeyError with “key{date} not found”