Click here to Skip to main content
15,881,413 members
Articles / Programming Languages / Ruby
Article

Simple Template Engine

Rate me:
Please Sign up or sign in to vote.
4.07/5 (10 votes)
19 Aug 2008CPOL3 min read 40.4K   330   38   1
Generate source code and database scripts for any language or platform.

Introduction

Code generation is a fundamental aspect of flexible, rapid application development. There are many open source and commercial solutions available, such as Microsoft T4 or CodeSmith Tools, but often times, these tools are targeted at specific technologies or platforms. With a little legwork, you can leverage template processing libraries in modern scripting languages such as Ruby or Python to create powerful, open-ended template engines. You can use these template engines with any language or platform to generate source code, database scripts, configuration files, or any other text-based file.

Background

This particular solution uses the open source Ruby language to process templates, given a simple, flexible XML schema representing your domain model. Ruby comes preconfigured on OS X systems, is easily installed through package managers on most popular Linux distributions, and may be installed on Windows platforms using the Ruby One-Click installer found at ruby-lang.org.

Using the code

This example will use the following trivial domain schema describing a library catalog:

XML
<Library catalog="MyCatalog">

    <enumeration name="Category">
        <value>FICTION</value>
        <value>NON_FICTION</value>
        <value>REFERENCE</value>
        <value>PERIODICAL</value>
    </enumeration>

    <enumeration name="Format">
        <value>BOOK</value>
        <value>MAGAZINE</value>
        <value>COMPACT_DISC</value>
        <value>VHS</value>
        <value>DVD</value>
    </enumeration>

    <object name="Media">
        <property name="ID" type="string" />
        <property name="Title" type="string" />
        <property name="Category" type="Category" />
        <property name="Format" type="Format" />
        <property name="Author" type="string" />
        <property name="ReleaseDate" type="DateTime" />
    </object>

    <object name="Author">
        <property name="ID" type="string"/>
        <property name="First" type="string"/>
        <property name="Last" type="string"/>
    </object>

</Library> 

The following template file Enumerations.erb could be used to generate C# enumerations from the library domain specification:

C#
namespace <%= catalog %>
{

    <%- enumerations.each do |enumeration| -%>
    public enum <%= enumeration.name %>
    {
        <%- enumeration.values.each do |value| -%>
        <%= value %><%= ',' if value != enumeration.values.last %>
        <%- end -%>
    }

    <%- end -%>
}

Running the command ruby generator.rb library.xml Enumerations.erb produces the following output:

C#
namespace MyCatalog
{

    public enum Category
    {
        FICTION,
        NON_FICTION,
        REFERENCE,
        PERIODICAL
    }

    public enum Format
    {
        BOOK,
        MAGAZINE,
        COMPACT_DISC,
        VHS,
        DVD
    }

}

The following template file Objects.erb can be used to generate simple C# domain objects from the library domain specification:

C#
using System;

namespace <%= catalog %>
{

    <%- objects.each do |object| -%>
    public partial class <%= object.name %>
    {
        <%- object.properties.each do |property| -%>
        <%= property.type %> <%= property.name %> { get; set; }
        <%- end -%>
    
        public object Clone()
        {
            <%= object.name %> copy = new <%= object.name %>();
            <%- object.properties.each do |property| -%>
            copy.<%= property.name %> = <%= property.name %>;
            <%- end -%>
            return copy;
        }
    }

    <%- end -%>
}

Running the command ruby generator.rb library.xml Objects.erb produces the following output:

C#
using System;

namespace MyCatalog
{

    public partial class Media
    {
        string ID { get; set; }
        string Title { get; set; }
        Category Category { get; set; }
        Format Format { get; set; }
        string Author { get; set; }
        DateTime ReleaseDate { get; set; }
        
        public object Clone()
        {
            Media copy = new Media();
            copy.ID = ID;
            copy.Title = Title;
            copy.Category = Category;
            copy.Format = Format;
            copy.Author = Author;
            copy.ReleaseDate = ReleaseDate;
            return copy;
        }
    }

    public partial class Author
    {
        string ID { get; set; }
        string First { get; set; }
        string Last { get; set; }
        
        public object Clone()
        {
            Author copy = new Author();
            copy.ID = ID;
            copy.First = First;
            copy.Last = Last;
            return copy;
        }
    }

}

If you need to add or modify domain objects, it is simply a matter of updating your XML schema. With the proper templates, you will no longer need to maintain your persistence layer or any other boilerplate code, and any bug fixes or modifications you make to your templates will be immediately available across all of your domain objects. Ultimately, you will spend less time maintaining your plumbing code, and more time doing real development.

Having your domain specification under revision control opens up some more possibilities. You will have a database backup and versioning scheme that corresponds with your repository revision (and possibly build numbers). You could reconstruct your database tables from any point in your repository. You could compare two versions of your schema and generate database rollback and change scripts. Setting up a database for a new developer will be a trivial task.

Points of interest

The Ruby script driving this template engine is very short. I have trimmed out some functionality for the sake of demonstration, but you will have access to the entire Ruby language within the engine as well as your templates, allowing you to extend the engine any way you want.

I've included another (trivial and poorly designed) domain specification to demonstrate the usage of domain specific XML tags. You can use any tag name you want, and specify any attribute you want. One requirement is that the name attribute must be present in any complex element. XML Text fields must also not be used for anything other than representing simple lists (as demonstrated by the following ingredient elements).

XML
<kingdom domain="Animal"/>

    <animal name="Duck" species="Bird">
        <recipe name="Bacon Wrapped Duck" difficulty="Medium">
            <ingredient>10 duck breasts</ingredient>
            <ingredient>3 lbs peppered bacon</ingredient>
            <ingredient>2 jars banana pepper rings</ingredient>
            <ingredient>Italian dressing</ingredient>
        </recipe>
        <recipe name="Plain Duck" difficulty="Easy"/>
        <feature name="Feet" value="Webbed" color="Yellow"/>
        <feature name="Tasty" value="true"/>
    </animal>
    
    ...

</kingdom>

The following template snippet would allow you to iterate through your recipes:

HTML
<%- animals.each do |animal| -%>
    <%- animal.recipes.each do |recipe| -%>

        ...

    <%- end -%>
<%- end -%>

If there is enough interest, I could throw together a more practical solution demonstrating schema validation, nullable data types, multiple languages, inheritance, persistence, and build integration. Feel free to e-mail me at baker.alex@gmail.com with any questions or comments.

Helpful links

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralUndocumented functionality. Pin
Andy Hass20-Aug-08 17:18
Andy Hass20-Aug-08 17:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.