Articles / Templates in Ruby

Templates in Ruby

Templates are a valuable tool in any programmer's toolkit. I'm not talking about C++ templates, in which new concrete classes are created by replacing variable types within a template class. I'm talking about text templates, in which a string contains markers for replacement items, which are replaced with values.

For example, a template might look like this:

Hello :::name:::, you owe us :::amount:::.

In this case the "template" is the string, and the replacement tokens are "name" and "amount", which can be replaced on the fly with new values.

What are templates used for?

Templates are useful in a whole variety of scenarios. Here are a few to stoke the fire in your imagination:

Mail Merge
The example above could easily be extended into a Mail Merge system in which the output is sent via email or snail mail to the customer.
Internationalization
You could use a template system in combination with different templates for different languages.
Making RTF Files
You could use Word to create an RTF file, which you could then use as a template (with plugin items) to create pretty reports.
Code Generation
Templates can be used to generate implementation files to save hand coding time and reduce the potential for error.
Quick HTML Pages
To make simple pages, you could use a lightweight template system, instead of rolling out the big guns (like JSP, HTML::Mason, or ASP).

Why not use HTML::Mason, JSP, or ASP?

Serious page markup languages like HTML::Mason, JSP, and ASP are excellent for doing all of the above. However, it's still valuable to have a small templating system that matches smaller requirements and needs. Several different sizes of templating systems are shown below; you can pick the right style to fit your needs.

Why use Ruby?

First, because I like it. Second, because I want to give Ruby some more exposure. Third, Ruby has regular expressions built in. Regular expressions are at the core of any template system, since the idea is to replace special tags with values that are dynamically generated.

Templating systems can be built in languages like C, C++, or Java, but you should look seriously into a regular expression library to make the implementation easier. Raw string parsing in C is no fun.

A Note on Files

The example code in this article uses template strings that are stored with the code to make understanding the examples easier. Most of the time, however, you will want to store a template in a local file so you can make alterations on it without changing the code.

To read a file into a string with a very short command, use: File::PrivoxyWindowOpen( "filename" ).read()

Simple Template Systems

Ruby itself has a sort of templating system built into it already; the string formatting system in Ruby can be used to do template replacements. The code below illustrates this:

# The template.  Notice the single quotes around this string to delay
# evaluation.

template = 'Dear #{customer},\nPlease pay us #{amount} now.\n'

# The "replacement" values.

customer = "Joe Schmoe"
amount = sprintf( "$%0.2f", 250 )

# Evaluate the string using eval, which will use the "replacement" values
# from the local context.

print eval( '"' + template + '"' )

We store the template in a string. The template uses the standard Ruby inline replacement syntax for variables. The standard syntax is #{expression}. We delay the replacement by using single quotes to define the string, then force the evaluation using the eval operator. Because we put the string in quotes within the eval, Ruby performs the string formatting evaluation, taking "customer" and "amount" from the current context and putting them into the string. The completed string is the implicit return value of the expression, which becomes the return value of the eval, which is then printed.

Tweaking the Simple System

The #{ and } formatting syntax is rather Ruby-specific. You might want to go with something that can be reused in different environments. In the example below, I use ":::" as the delimiter, so that tokens look like :::token_name::: within the template.

# The template

template = 'Dear :::customer:::,\nPlease pay us :::amount::: now.\n'

# The replacement values

customer = "Joe Schmoe"
amount = sprintf( "$%0.2f", 250 )

# The regular expression substition calls turn a string like this:
#   'Hi :::name:::.  How are you :::time:::'
# Into:
#   'Hi #{name}.  How are you #{now}'

template.gsub!( /:::(.*?):::/ ) { '#{'+$1+'}' }

# Then we evaluate the string using Ruby's built in string handling

print eval( '"' + template + '"' )

Here, we replace :::token_name::: with #{token_name} before the string is evaluated so that we get back to using standard Ruby string formatting as we had it in the original system.

A Regular Expressions Approach

For a variety of reasons, you may not want to use eval for templates. In particular, if security is a concern, you want to avoid allowing arbitrary code to be evaluated. The example below shows a use of regular expressions to do replacement:

# template: Returns a string formed from a template and replacement values
#
# templateStr - The template string (format shown below)
# values - A hash of replacement values
#
# Example format:
#   "Dear :::customer:::,\nPlease pay us :::amount:::.\n"
#
# The "customer" and "amount" strings are used as keys to index against the
# values hash.
#

def template( templateStr, values )
  templateStr.gsub( /:::(.*?):::/ ) { values[ $1 ].to_str }
end


print template( "Dear :::customer:::,\nPlease pay us :::amount::: now.\n",
  { 'amount' => sprintf( "$%0.2f", 250 ), 'customer' => 'Joe Schmoe' } )

The fact that I used a function to make this happen is really irrelevant. The main work happens in the gsub call within the template function. The regular expression used with gsub finds the replacement tokens, gets the token name, and puts it in $1. The block attached to gsub is called when a match is found, and the result of the block is put back into the string. In this case, $1 is used as an index of the values hash to get the correct replacement value for the string.

Now is probably a good time to stress that you should learn to love regular expressions (RegExps for short) if you haven't already. Once you get the hang of regular expressions, you will never want to go back to hand parsing text input.

A Different Take on the RegExp Approach

There are always many ways to do the same thing. To illustrate that in our current problem, here is another take on the idea:

# template: Returns a string formed from a template and replacement values
#
# templateStr - The template string (format shown below)
# values - A hash of replacement values
#
# Example format:
#   "Dear :::customer:::,\nPlease pay us :::amount:::.\n"
#
# The "customer" and "amount" strings are used as keys to index against the
# values hash.
#

def template( templateStr, values )
  outStr = templateStr.clone()
  values.keys.each { |key|
    outStr.gsub!( /:::#{key}:::/, values[ key ] )
  }
  outStr
end


print template( "Dear :::customer:::,\nPlease pay us :::amount::: now.\n",
  { 'amount' => sprintf( "$%0.2f", 250 ), 'customer' => 'Joe Schmoe' } )

In the previous implementation, we used the regular expression to drive the search and to find the tags, which were then replaced by using the interior value of the token as an index of a hash. In this implementation, we reverse the flow. We use the array of keys to drive the string replacement. The regular expression /:::#{key}:::/ matches only the key that we are looking for, so we replace it with the value of just that key.

My hunch is that this method is actually slower than the previous one, since in the previous example, the regular expression engine was only tasked with one set of replacements, so the string shuffling was kept to a minimum.

All these functions, though... Isn't Ruby an Object Oriented language? The next section demonstrates an OO approach to templates.

Text Templates As Objects

Templates work well as objects. Encapsulation allows them to be passed around as arguments to other classes and functions. Ruby makes that all very easy. This example is an initial pass at a class-based implementation:

# Template: Implements a string substituation template.
# 
# The template should look like this:
#
# 'Hi :::name:::, how are you doing :::time:::'
#
# With this template value, you could use the set method to set
# the values "name" and "time" to their appropriate replacement
# values.

class Template

  # Construct the template object

  def initialize( template )
      @template = template.clone()
    @values = {}
  end

  # Set the value of a replacement variable. The name is the 
  # name in the template.  The value is the string to replace
  # the corresponding item(s) in the template.
  
  def set( name, value )
      @values[ name ] = value
  end

  # Run the template with the given parameters and return
  # the template with the values replaced
  
  def run()
    @template.gsub( /:::(.*?):::/ ) { @values[ $1 ].to_s }
  end

  # A synonym for run so that you can simply print the class
  # and get the template result

  def to_s()
      run()
  end
end

The constructor takes the text string and successively calls the set method create associations between the replacement tokens and the values to replace them with. The run method then builds a new string from the template with the values replaced. The override of the to_s method is a convenience that allows the class to be converted to a string to force the evaluation.

Since the template string stored within the object is not altered when run is invoked, the same template object can be used multiple times with different calls to give different results.

Here's an example use of the class:

require "Template"

# Create the template object with the template string

temp = Template.new( "Dear :::customer:::,\nPlease pay us :::amount::: now.\n" )

# Set the names and values of the replacement items

temp.set( 'customer', 'Joe Schmoe' )
temp.set( 'amount', sprintf( "$%0.2f", 250 ) )

# Evaluate and print the template

print temp

The class code is stored in a file named "Template.rb", so this test is requiring that file. The template is created with new, then the associations are made with the invocations of set, and the template is evaluated by the call to print. The print call invokes to_s, which in turn invokes run.

Making Debugging Easier

A common problem with text templating systems is a mismatch between the key name in the template and the key name in the code. To help diagnose this problem, you may want to add a runtime exception when the key found in the template has not been associated with anything in the object. The code below illustrates this:

# Template: Implements a string substituation template.
# 
# The template should look like this:
#
# 'Hi :::name:::, how are you doing :::time:::'
#
# With this template value, you could use the set method to set
# the values "name" and "time" with their appropriate replacement
# values.

class Template

  # Construct the template object

  def initialize( template )
      @template = template.clone()
    @values = {}
  end

  # Set the value of a replacement variable. The name is the 
  # name in the template.  The value is the string to replace
  # the corresponding item(s) in the template.
  
  def set( name, value )
      @values[ name ] = value
  end

  # Run the template with the given parameters and returns
  # the template with the values replaced
  
  def run()
    @template.gsub( /:::(.*?):::/ ) {
        raise "Key '#{$1}' found in template but the value has not been set" unless ( @values.has_key?( $1 ) )
        @values[ $1 ].to_s
    }
  end

  # A synonym for run so that you can simply print the class
  # and get the template result

  def to_s()
      run()
  end
end

Now the run code checks to make sure that the key found in the template is actually a value in the @values hash. If it's not, an exception is raised.

Here is the updated test code that causes the exception as a test:

require "Template"

# Create the template object with the template

temp = Template.new( "Dear :::customer:::,\nPlease pay us :::amount::: now.\n" )

# Set the first template name/value replacement pair

temp.set( 'customer', 'Joe Schmoe' )

# We expect this to fail, since we haven't set "amount"

begin
  print temp
rescue => errStr 
  print "Template substitution didn't work:\n#{errStr}\n\n"
end

# Set the amount value

temp.set( 'amount', sprintf( "$%0.2f", 250 ) )

# We expect this to work since both "customer" and "amount" are set

begin
  print temp
rescue => errStr 
  print "Template substitution didn't work:\n#{errStr}\n\n"
end

First, we try without having both "customer" and "amount" set. That fails, but when we try again with both set, it works.

Finding errors is one thing. Making errors less frequent is good preventive medicine. One way to do that would be to coerce all of the key values to lower or upper case to prevent case-sensitive errors. That exercise is left to the reader.

Methods and Functions as Replacement Values

One thing we lost in going from an eval system to a regular expression-based system was that the replacement tokens in an eval could be Ruby expressions that were more than just straight variable replacements. Sometimes, simple variable replacement isn't enough. You will often find yourself wishing for a simple expression within the replacement tag, or you may have a tag that has a complex syntax that doesn't easily fit into a simple key/value pairing. That's where this next example class comes in. In this class, the set of values can be either a hash or a method:

# Template: Implements a string substituation template.
# 
# The template should look like this:
#
# 'Hi :::name:::, how are you doing :::time:::'
#
# With this template value, you could use the set method to set
# the values "name" and "time" with their appropriate replacement
# values.

# This version of the template class takes either a hash for values,
# or a method or function.  The function takes a single parameter, which
# is the key, and returns a single value, which is the string to be used as a
# replacement.

class Template

  # Construct the template object with the template and the
  # replacement values.  "values" can be a hash, a function,
  # or a method.

  def initialize( template, values )

      @template = template.clone()

    # Notice that we normalize the cases of hash and method to just
    # having a method, using the "fetch" method from hash as the method
    # to call when we need a value.

    if values.kind_of?( Hash )
      @values = values.method( :fetch )
    else
      @values = values.clone()
    end
  end

  # Run the template with the given parameters and return
  # the template with the values replaced
  
  def run()
    @template.gsub( /:::(.*?):::/ ) { @values.call( $1 ).to_s }
  end

  # A synonym for run so that you can simply print the class
  # and get the template result

  def to_s() run(); end
end

Notice that we removed the set method and replaced that functionality with a single extra value on the constructor. This value is either a hash or a method. Because Ruby is an excellent dynamic language, we are able to normalize both the hash and method cases to both be method calls. This works because we get a method reference to the fetch method of the hash, which has the same signature as our target method. It takes one string and returns one value.

Also notice that the raise code is gone from the previous example, since it is no longer appropriate for all use cases.

Test code for this class is shown below:

require "Template"

# Create the template object with the template and a hash

temp1 = Template.new( "Dear :::customer:::,\nPlease pay us :::amount::: now.\n",
  { 'customer' => 'Joe Schmoe', 'amount' => sprintf( "$%0.2f", 250 ) } )

# Evaluate the template with the substitution values

print temp1


# Create a new function that is going to be called by the template when it
# needs the values of the replacement items

def myItems( item )
  return 'Joe Schmoe' if ( item == "customer" )
  return sprintf( '$%0.2f', 250 ) if ( item == "amount" )
end

# Create the template object with the template and a method

temp2 = Template.new( "Dear :::customer:::,\nPlease pay us :::amount::: now.\n", method( :myItems ) )

# Evaluate the template with the substitution values

print temp2

The first set of tests around the temp1 object test the hash-driven API. The second test around the temp2 object tests the method- or function-driven API by sending a method reference to the myItems function.

I have to stand back for a second and say that this method referencing functionality is very impressive and a great feature of Ruby. What's important to understand is that by calling method on an object, you get a method reference to that method on that object. In C++, you can get a pointer to a member function, but would have to store the pointer to self separately. Method functionality in Ruby handles this for you. The object returned from the method code stores both the self reference and the method reference. This very cool, and yet another reason to love Ruby.

Allowing Multiple Replacement Value Sets

I haven't run into this very often, but I have seen cases in which you want different sets of replacement values. For example, you may have one set of replacements for constants and another for values.

This last Template class example allows you to register several different tag sets to look for, which can be associated with either hash values or methods.

# Template: Implements a string substituation template.
# 
# The template should look like this:
#
# 'Hi :::name:::, how are you doing :::time:::'
#
# With this template value, you could use the set method to set
# the values "name" and "time" with their appropriate replacement
# values.

# This version of the template class takes either a hash for values,
# or a method or function.  The function takes a single parameter,
# which is the key, and returns a single value, which is the string to
# be used as a replacement.

class Template

  # Construct the template object with the template and the
  # replacement values.  "values" can be a hash, a function,
  # or a method.

  def initialize( template )
      @template = template.clone()
    @replaceStrs = {}
  end

  # Set up a replacement set.
  #
  # replaceStr: The string that starts and ends a replacement
  # item. For example, ":::" would mean that the replacement tokens
  # look like  :::name:::.
  #
  # values: The hash of values to replace the replacement token with,
  # or a method to call with a key.

  def set( replaceStr, values )
    if values.kind_of?( Hash )
      @replaceStrs[ replaceStr ] = values.method( :fetch )
    else
      @replaceStrs[ replaceStr ] = values.clone()
    end
  end

  # Run the template with the given parameters and return
  # the template with the values replaced
  
  def run()
      outStr = @template.clone()
      @replaceStrs.keys.each { |replaceStr|
      outStr.gsub!( /#{replaceStr}(.*?)#{replaceStr}/ ) {
          @replaceStrs[ replaceStr ].call( $1 ).to_s
      }
    }
    outStr
  end

  # A synonym for run so that you can simply print the class
  # and get the template result

  def to_s() run(); end
end

Now the constructor is back to taking a single format text string, and we have brought back the set method. In this case, though, the set method takes two values. The first specifies what will bracket the token name in the format, such as ":::" or "!!!" or whatever you like. The second is the hash or method to use when looking up the value for the replacement token.

The run method in this class then uses this list of replacement sets to make successive groups of search and replace operations to turn the template string into the output string.

Here's example code that uses this class:

require "Template"

# Create the template object with the template and a hash

temp = Template.new( "Dear :::customer:::,\nPlease pay us :::amount::: now.\n" )

temp.set( ":::", { 'customer' => 'Joe Schmoe', 'amount' => sprintf( "$%0.2f", 250 ) } )

# Evaluate the template with the substitution values

print temp

The code is fairly simple. We build the template class with the template string, set up the replacement set with the set method, then evaluate the template with the replacement values using the to_s method (invoked by print).

Conclusions

Text templating is a powerful tool that should be in every programmer's back pocket. Often, I find that invoking the true power of a large-scale templating language is just too much for a particular problem. In these cases, simple text templating systems like the ones described in this article come in very handy.

Special thanks to Dru Nelson for his help on this article.

RSS Recent comments

08 Jun 2002 09:33 CJayC

Thanks.
I'd just like to take a minute to thank you, Mr. Herrington, for this excellent tutorial. I feel that I have so much more power right at my finger tips.

Thanks,
Christopher "CJayC" Jenkins

19 Nov 2002 07:15 krono

good article
I'd like to thak for this. I think, i've now ideas for ab bit contentmanaging..

18 May 2003 06:58 Puttel

Good
Very good tutorial. Has helped me alot :-)

03 Apr 2007 08:45 niravdani

Nice!
Excellent! Thanks!

Screenshot

Project Spotlight

Bible-Discovery

Bible study and concordance software.

Screenshot

Project Spotlight

libnftnl

A userspace library providing a netlink programming interface to the nf_tables subsystem.