Problem
You want to create a string that contains Ruby expressions or variable substitutions, without actually performing the substitutions. You plan to substitute values into the string later, possibly multiple times with different values each time.
Solution
There are two good solutions: printf-style strings, and ERB templates.
Ruby supports a printf-style string format like C's and Python's. Put printf directives into a string and it becomes a template. You can interpolate values into it later using the modulus operator:
template = 'Oceania has always been at war with %s.' template % 'Eurasia' # => "Oceania has always been at war with Eurasia." template % 'Eastasia' # => "Oceania has always been at war with Eastasia." 'To 2 decimal places: %.2f' % Math::PI # => "To 2 decimal places: 3.14" 'Zero-padded: %.5d' % Math::PI # => "Zero-padded: 00003"
An ERB template looks something like JSP or PHP code. Most of it is treated as a normal string, but certain control sequences are executed as Ruby code. The control sequence is replaced with either the output of the Ruby code, or the value of its last expression:
require 'erb' template = ERB.new %q{Chunky <%= food %>!} food = "bacon" template.result(binding) # => "Chunky bacon!" food = "peanut butter" template.result(binding) # => "Chunky peanut butter!"
You can omit the call to Kernel#binding if you're not in an irb session:
puts template.result # Chunky peanut butter!
You may recognize this format from the .rhtml files used by Rails views: they use ERB behind the scenes.
Discussion
An ERB template can reference variables like food before they're defined. When you call ERB#result, or ERB#run, the template is executed according to the current values of those variables.
Like JSP and PHP code, ERB templates can contain loops and conditionals. Here's a more sophisticated template:
template = %q{ <% if problems.empty? %> Looks like your code is clean! <% else %> I found the following possible problems with your code: <% problems.each do |problem, line| %> * <%= problem %> on line <%= line %> <% end %> <% end %>}.gsub(/^s+/, '') template = ERB.new(template, nil, '<>') problems = [["Use of is_a? instead of duck typing", 23], ["eval() is usually dangerous", 44]] template.run(binding) # I found the following possible problems with your code: # * Use of is_a? instead of duck typing on line 23 # * eval() is usually dangerous on line 44 problems = [] template.run(binding) # Looks like your code is clean!
ERB is sophisticated, but neither it nor the printf-style strings look like the simple Ruby string substitutions described in Recipe 1.2. There's an alternative. If you use single quotes instead of double quotes to define a string with substitutions, the substitutions won't be activated. You can then use this string as a template with eval:
class String def substitute(binding=TOPLEVEL_BINDING) eval(%{"#{self}"}, binding) end end template = %q{Chunky #{food}!} # => "Chunky #{food}!" food = 'bacon' template.substitute(binding) # => "Chunky bacon!" food = 'peanut butter' template.substitute(binding) # => "Chunky peanut butter!"
You must be very careful when using eval: if you use a variable in the wrong way, you could give an attacker the ability to run arbitrary Ruby code in your eval statement. That won't happen in this example since any possible value of food gets stuck into a string definition before it's interpolated:
food = '#{system("dir")}' puts template.substitute(binding) # Chunky #{system("dir")}!
See Also
Strings
Numbers
Date and Time
Arrays
Hashes
Files and Directories
Code Blocks and Iteration
Objects and Classes8
Modules and Namespaces
Reflection and Metaprogramming
XML and HTML
Graphics and Other File Formats
Databases and Persistence
Internet Services
Web Development Ruby on Rails
Web Services and Distributed Programming
Testing, Debugging, Optimizing, and Documenting
Packaging and Distributing Software
Automating Tasks with Rake
Multitasking and Multithreading
User Interface
Extending Ruby with Other Languages
System Administration