Skip to main content

Add Ruby templating to your Project Zero and WebSphere sMash applications

Extend Ruby support with RHTML files to create dynamic user interfaces

Dan Jemiolo (danjemiolo@us.ibm.com), Advisory Software Engineer, IBM 
Dan Jemiolo is an Advisory Software Engineer on IBM's Project Zero team in Research Triangle Park, NC. He is currently working on reusable components for the Zero platform and its service catalog. His previous work includes the design and development of Apache Muse 2.0 and participation in OASIS Web services standards bodies. Dan came to IBM three years ago after earning his Master of Science degree in Computer Science from Rensselaer Polytechnic Institute.

Summary:  Ruby users, take note. You can now do everything that Groovy and PHP users can do when creating Project Zero applications! In a previous article, we showed how to augment Project Zero to provide support for the Ruby scripting language. The code that we wrote enabled Ruby users to transfer their scripting skills to the Zero platform and take advantage of its unique programming model. Of course, scripting isn't the only way that Ruby is used to create applications - programmmers who use the Ruby on Rails framework also mix Ruby in HTML templates similar to JSP and PHP. These templates, called RHTML files, are very useful for creating dynamic user interfaces, and this article will show you how to extend our Ruby support to include them. Find out how Ruby users can now do everything that Groovy and PHP users can do when creating Zero applications!

Date:  22 Jan 2008
Level:  Intermediate
Activity:  1785 views

Editor's note: IBM® WebSphere® sMash and IBM WebSphere sMash Developer Edition are based on the highly acclaimed Project Zero incubator project. Project Zero is the development community for WebSphere sMash and will continue to offer developers a cost-free platform for developing applications with the latest builds, the latest features, and the support of the community.

Before you get started

This article assumes that you have downloaded Project Zero M2 and either completed the introductory tutorial or written a simple application yourself. You should also read the predecessor to this article, "Add Ruby scripting to your Project Zero applications" and understand the concepts behind server-side template languages like JSP, PHP, and RHTML.

The Project Zero community
Take a stroll around the Project Zero Web site and see how Project Zero provides a powerful but radically simple development and execution platform for modern Web applications. The active community discusses project development, provides help to developers, and wants to hear your ideas!

Introduction

In the predecessor to this article, "Add Ruby scripting to your Project Zero applications," you learned how Project Zero could be augmented to provide support for the Ruby scripting language, but our solution did not cover all of the ways in which Ruby on Rails developers use Ruby to create applications. The Rails framework includes a way to mix Ruby code with HTML templates, an idea that is similar to JSP and PHP. These templates, called RHTML files, are very useful for creating dynamic user interfaces, and prevent the programmer from having to use print statements to create HTML (as with J2EE servlets). Zero already allows users to create templates with Groovy and PHP (.gt and .php files, respectively), so why shouldn't the same be true of Ruby?

Strategy for creating an RHTML processor

When deciding how to implement an RHTML processor for Zero, our main goal is to avoid writing an RHTML parser and interpreter from scratch. Ruby templating is a nice feature, but not so nice that it's worth dragging in a large set libraries and writing compiler code that will be fairly bug-ridden until a large number of users have dedicated time to testing it. With Ruby scripts, we were able to delegate all of the script execution work to JRuby; it would be nice to have JRuby handle the code embedded in our RHTML files in a similar manner so that we didn't have to write our own specialized Ruby interpreter.

The trouble, of course, is that JRuby cannot execute RHTML files, only Ruby scripts that have valid Ruby syntax. How can we make it so that the Ruby code embedded in the RHTML file is executed by JRuby while the rest of the content (plain HTML) is sent to the client without modification? And if we do manage to extract the Ruby code and pass it off to JRuby, how do we integrate the output of the code with the static HTML? When you consider the fact that the various snippets of Ruby code spread across the file are all meant to be executed in the same scope (with access to each other's variables), it becomes clear that separating the code from the markup and passing it to JRuby is not sufficient. We need a better way.

The real answer is to convert the entire RHTML file -- code and markup -- into a Ruby script, similiar to how a JSP compiler turns JSP files into J2EE servlet classes. Once the template is a valid Ruby script, we can pass it along to JRuby without the interpreter even being aware of its origins. The following sections will discuss the details of RHTML syntax and the conversion process that will be central to our RHTML processor.

Understanding the RHTML syntax

RHTML files are HTML files that have Ruby code embedded inside special tags: <% and %>. Everything inside these special tags is processed on the server before the page is returned to the client, so the end user can never see the original Ruby code. The Ruby code must have one or more complete Ruby statements, and statements in one group (<% ... %>) are affected by statements that were executed in previous groups. The fact that code snippets can be broken up into multiple groups helps programmers avoid situations where they have to generate HTML with print statements, creating a clean separation of code and markup. Listing 1 shows an example of RHTML content:


Listing 1. Sample RHTML file with embedded statements
                
    
    <html>
        
      <body>
        <p>
        Here is a list of numbers 1 - 10:
        <%
          (1..10).each do |n| 
            print n, ' '
          end
        %>
        </p>
      </body>
    
    </html>
      

There is one special case to consider when embedding Ruby code into HTML: if the developer just wants to insert a single value or the result of a simple expression, the start tag can be modified to be <%=. >Listing 2 has an example of embedding simple values into a page without using complete statements.


Listing 2. Sample RHTML file with embedded expressions
                
      
    <html>
        
      <body>
        <p>
        The sum of 3 and 4 is <%= 3 + 4 %>
        </p>
      </body>
    
    </html>
      

Generating valid Ruby scripts

In order to generate Ruby scripts from files like those in Listings 1 and 2, we will need to create Ruby statements to represent the static HTML as well as the embedded code. All non-Ruby text (such as HTML and JavaScript) should be converted into print statements that output the string literals. All Ruby code should be copied in as-is, with print statements (for HTML) interspersed as necessary. The special case of embedded values (<%= ... %>) will require you to create a print statement that outputs the Ruby expression found between the tags. Listing 3 shows what a generated script should look like for the file in Listing 2:


Listing 3. Ruby script equivalent of RHTML file
                
    
    print "<html><body><p>The sum of 3 and 4 is "
    print 3 + 4
    print "</p></body></html>"
      

As you can see, the scripts should be conceptually simple -- they are mostly print statements, with other Ruby statements mixed in as appropriate. One optimization that could be made to the code in Listing 3 is the use of newline characters to make the generated HTML look more like the original file -- this would make it much easier for developers to debug their RHTML templates. We will include this optimization in our own implementation.

Storing the Ruby scripts

The final decision that we have to make when planning for our RHTML processor is what to do with the scripts once they've been generated. The easiest thing to do would be to write the script to disk with a file name that could be easily mapped to the original RHTML file. For example, if the original file were named my-file.rhtml, the generated script could be my-file.rhtml.rb (.rb being the usual extension for Ruby scripts). Our processor can then use the following algorithm to generate and execute scripts:

  1. Create file name for the script that will represent the RHTML file.
  2. If the script exists and the RHTML file has not been recently modified, skip generation.
  3. Otherwise, generate the script and save it to disk using the name from Step 1.
  4. Execute the script.

Step 2 serves two purposes: it's an optimization because it prevents us from generating the same script over and over again, and it's a convenience for developers because changes they make to an RHTML file will show up immediately with the next request (a big time saver during development time).

Implementing the RHTML processor

Now that we have a strategy for implementing the RHTML processor, we need to relate it to our original Ruby processor. As with the previous article, you can follow along by adding code step-by-step to your sample project, or you can download the completed project from the Download section. If you're following along, you will want to start with the sample project you used for the last article so that the regular Ruby support is already in place.

Currently our Ruby support is handled by a class named JRubyInterpreter that implements Zero Core's Interpreter interface and executes HTTP requests using the JRuby interpreter. Processing RHTML files will require us to write another Interpreter class that first generates a Ruby script before invoking JRuby. We can extend the JRubyInterpreter class to avoid rewriting the JRuby-oriented code. Listing 4 shows an implementation of an RHTMLInterpreter class that does what we want.


Listing 4. The RHTMLInterpreter class
                
    
    package zero.scripting.ruby;

    import java.io.File;

    import zero.core.context.GlobalContext;
    import zero.core.events.HandlerInfo;
    import zero.scripting.html.HTMLCompiler;

    public class RHTMLInterpreter extends JRubyInterpreter 
    {
        public void invoke(HandlerInfo handlerInfo) 
        {
            try 
            {
                File scriptFile = new File(handlerInfo.handler);
                HTMLCompiler compiler = new HTMLCompiler();
                
                //
                // convert RHTML file to a Ruby script file
                //
                File compiledFile = compiler.compile(scriptFile, ".rb");
                
                super.invoke(compiledFile);
            } 
            
            catch (Throwable error) 
            {
                GlobalContext.put("/request/status", 500);
                GlobalContext.put("/request/error", error);
            }
        }
    }
      

Notice that the RHTMLInterpreter.invoke() method uses a class named HTMLCompiler to handle the dirty work of converting RHTML to Ruby; it's only real responsibility is to ensure that this happens before delegating execution to the parent class. The HTMLCompiler class is large, but the compile() method used above is the most important part. Listing 5 shows the code from the compile() method. You can get the rest of the code from the Download section.


Listing 5. The HTMLCompiler.compile() method
                
    
    public File compile(File htmlFile, String extension)
        throws IOException, HTMLCompilerException
    {
        File compiledFile = getCompiledFile(htmlFile, extension);
        
        //
        // make sure we don't re-compile files that haven't changed
        //
        if (compiledFile.exists() && 
            compiledFile.lastModified() > htmlFile.lastModified())
            return compiledFile;
        
        String html = getHTML(htmlFile);
        
        int current = 0;
        int codeStart = html.indexOf(_EMBEDDED_START_TOKEN, current);
        
        FileWriter writer = new FileWriter(compiledFile);
        
        //
        // look for the code fragments and add them to the script as 
        // regular code statements. all HTML gets converted into 
        // print statements
        //
        while (codeStart >= 0)
        {
            //
            // record all HTML preceding next code fragment
            //
            writePrintStatements(writer, html.substring(current, codeStart));
            
            int codeEnd = html.indexOf(_EMBEDDED_END_TOKEN, codeStart);
            
            if (codeEnd < 0)
                throw new HTMLCompilerException("No matching end token found.");
            
            int stmtStart = codeStart + _EMBEDDED_START_TOKEN.length();
            String code = html.substring(stmtStart, codeEnd);
            
            //
            // special case - for <%= expression %> fragments, we just 
            // want to insert the expression's value, not make it a 
            // separate statement
            //
            if (html.charAt(stmtStart) == _EMBEDDED_VALUE_TOKEN)
                writeValuePrintStatement(writer, code.substring(1));
            
            //
            // normal case - code recorded as it was written
            //
            else
            {
                writer.write(code);
                writer.write('\n');
            }
            
            current = codeEnd + _EMBEDDED_END_TOKEN.length();
            codeStart = html.indexOf(_EMBEDDED_START_TOKEN, current);
        }
        
        //
        // take care of HTML after last code fragment, if any
        //
        if (current < html.length())
            writePrintStatements(writer, html.substring(current));
        
        writer.flush();
        writer.close();
        
        return compiledFile;
    }
      

Notice that the compile() method includes the optimization specified by Step 2 of our algorithm. The first if statement makes sure that we only generate a script if we really need to. Also, if you look at the code for the writePrintStatements() method, you will see that it includes one newline character for every one found in the original file, so the generated HTML will look almost exactly the same.

Updating the Zero configuration file

Our code is in place, but we still have one more step to complete: like the Ruby interpreter before it, our RHTML interpreter must be registered with Zero Core so that it knows how to handle requests for .rhtml files. Adding the stanza from Listing 6 to your application's /config/zero.config file will make things official.


Listing 6. Add to your application's /config/zero.config file
                
    
    [/app/interpreters]
    .rhtml=zero.scripting.ruby.RHTMLInterpreter
      

Testing Ruby templates

Now that our RHTML support is in place, let's test it out by adding an RHTML file to our application and invoking it from a Web browser. To illustrate the complete integration of Ruby and RHTML with Zero, our template will use Zero's GlobalContext API to read request data and create a response. The RHTML file in Listing 7 shows the entire contents of the global context by serializing it within the HTML response.


Listing 7. RHTML file for testing our code
                
    
    <%require "java"%>
    <%include_class "zero.core.context.GlobalContext"%>
    <%include_class "zero.core.context.SimpleFormatter"%>
    <html>
      <head>
      <title>Sample RHTML Page</title>
      </head>
      <body>
        <p>The current contents of the global context are:
          <pre>
            <%
              formatter = SimpleFormatter.new
              GlobalContext.dump formatter
              print formatter
            %>
          </pre>
        </p>
      </body>
    </html>
      

Add the markup from Listing 7 to a file named test-ruby-and-markup.rhtml in your application's /public directory. You should then start the Zero application with the zero run command and test things out by pointing your favorite Web browser to http://localhost:8080/test-ruby-and-markup.rhtml. The result should be a simple HTML page that displays the contents of the global context. Figure 1 shows a screenshot of what the page looks like in Mozilla Firefox.


Figure 1. Screenshot of HTML page created by our Ruby template
Figure 1. Screenshot of HTML page created by our Ruby template

Conclusion

Being able to implement Zero applications with Ruby is great, but having the template features of Rails is even better. With the code that we wrote in this article, Ruby users can now do everything that Groovy and PHP users can do when creating Zero applications. Finally, the strategy that we used for converting HTML with embedded code into executable scripts is fairly generic and can be used to add more powerful templating features or new languages to the platform if desired.



Download

DescriptionNameSizeDownload method
Sample JRuby integration code and test fileszero.scripting.zip8419 KB HTTP

Information about download methods


Resources

Learn

Get products and technologies

  • Download Project Zero M2 and start applying the best practices covered in this article.

  • Download JRuby 1.0 to build and run the code included with this article.

Discuss

About the author

Dan Jemiolo

Dan Jemiolo is an Advisory Software Engineer on IBM's Project Zero team in Research Triangle Park, NC. He is currently working on reusable components for the Zero platform and its service catalog. His previous work includes the design and development of Apache Muse 2.0 and participation in OASIS Web services standards bodies. Dan came to IBM three years ago after earning his Master of Science degree in Computer Science from Rensselaer Polytechnic Institute.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development, Sample IT projects, WebSphere
ArticleID=282780
ArticleTitle=Add Ruby templating to your Project Zero and WebSphere sMash applications
publish-date=01222008
author1-email=danjemiolo@us.ibm.com
author1-email-cc=ruterbo@us.ibm.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers