Ruby Templates
In the simplest string processing in Ruby, you can "interpolate" the result of any Ruby expression by using #{...} inside a double-quoted string. This is really handy for string formatting and exists in most other scripting languages, including all the unix shells and perl. For example:
% irb
irb(main):001:0> puts "Today's date is #{Time.new}."
Today's date is Sat Mar 10 22:56:39 -0500 2007.
A template system is just a more elaborate method for interpolation. It is like a fancy "mail merge". There are many templating packages for different programming languages and they are very popular for web programming. I'll explain why in a moment. The key idea of most template systems is the ability to take a simple document and include special tags that are dynamically replaced by computed values. In our above example it was #{...}.
Ruby has a system called ERB. The syntax for interpolation is <% ... %>, <%= ... %>. For example, you might have a file of the form
Subpoena to appear in court:
<%= Time.new %>
Dear <% sth.fetch do { |row| puts "#{row['firstname']} #{row['lastname']}," } %>
You are scheduled to appear in court tomorrow at 9AM at the courthouse on Main St.
which might produce
Subpoena to appear in court:
Sat Mar 10 22:56:39 -0500 2007
Dear John Smith,
You are scheduled to appear in court tomorrow at 9AM at the courthouse on Main St.
In the first case (note the '=' sign), the <%= Time.new %> form is replaced by the value of the statement. In the second case, the statements inside the <%...%> tag are executed. It is replaced by any statements that write to stdout.
Now you might be able to imagine, then how this is quite useful for web programming when you have a general structure of HTML to send to a user, but it needs to be customized for that person's session. For example, you might have a file that looks like the following, where the lines before the <HTML> tag just provide boilerplate modules and initialization for web programming:
hello.rhtml
% require 'cgi'
% require 'cgi/session'
% require 'cgi/session/pstore'
% cgi=CGI.new('html3')
% sess=CGI::Session.new(cgi, 'database_manager' => CGI::Session::PStore)
<%= cgi.header('text/html') %>
<HTML>
<BODY>
<% sess['visits'] ||= 0 %>
Welcome! You have visited this site <%= sess['visits'] %> times.
<% if cgi.has_key?('foo') then %>
I also see that you've set parameter foo to <%=cgi['foo']%>. Thanks.
<% end %>
<p>
<%= cgi.a('hello.rhtml?foo=bar') { "Reload" } %>
</BODY>
</HTML>
<% sess['visits'] += 1 %>
Try it at http://cs445.cs.umass.edu/erb-wrapper/test445/hello.rhtml . Reload the page multiple times to see the counter increase.
Some Details of Web Programming
Web programming concepts are not hard. The main interaction between a web server and a client (a web browser) is by passing parameters, which can occur in 4 common ways:
- via name=value parameters after a question mark in a URL, e.g.
http://foo.bar/baz?name=val (called "GET"),
- via name/value pairs stored in the page request and response headers (aka "cookies"),
- via name/value pairs that are sent in forms (called "POST").
- via data embedded in the path of the URL, e.g.
http://foo.bar/baz/val
All of this is abstracted into a "CGI" class (which stands for "common gateway interface" and formally refers to the standards for passing environment variables to server scripts).
Reviewing the simple example, above, everything that's inside the <%...%> is Ruby code that is executed on the server before sending the file to the client. But the evaluation of the Ruby code is done based on information sent from the client that can be found in the CGI and session objects. The CGI object provides access to parameters set via GET and POST. It also provides a programmatic way of generating HTML tags, such as the a hyperlink tag and the required header that must begin any HTML file. The session object provides persistence between pages for a single web browser. That is, name/value pairs can be stored on the server and associated with a specific user "session ID". Using a cookie, that is, a single unique tag sent in the header of each request and response, the server can retrieve data stored from a previous request.
So above, we see that the cgi and session objects are initialized in the boilerplate preamble and then cgi.header is called to begin the file. Behind the scenes this will generate an HTTP header indicating the MIME type (format) of the file that will be sent (in this case, HTML) and ask the client to store a cookie, which is just a unique identifier called _session_id that the client maintains between requests so that the server can maintain state.
% require 'cgi'
% require 'cgi/session'
% require 'cgi/session/pstore'
% cgi=CGI.new('html3')
% sess=CGI::Session.new(cgi, 'database_manager' => CGI::Session::PStore)
<%= cgi.header('text/html') %>
will generate
Content-Type: text/html
Set-Cookie: _session_id=c4fea0d65605e7e3
Next, you'll see a common Ruby idiom using the "OR-EQUALS" operator. If the l.h.s. returns a true value, then nothing gets added to it, but if it is not defined, then the l.h.s. will be false. It will then get ORd with the r.h.s., which sets it equal to the r.h.s. value. In short, x ||= y will set x to y if x is not already defined.
The items that got initialized is a value in the session hash, sess['visits']. You can use any key value you want. Values set in the session hash are available in future page requests. The value of sess['visits'] is then output.
<% sess['visits'] ||= 0 %>
Welcome! You have visited this site <%= sess['visits'] %> times.
produces on the first page load
Welcome! You have visited this site 0 times.
Most of the remainder of the page simply displays HTML with interpolated values. But importantly, you can see that conditionals (as well as loops) allow segments of the page to be included or excluded. Here, we check whether the client has sent the server a value for the parameter foo using either GET or POST. If so, then its value is printed.
Lastly, you see an example of using the cgi module's functions for generating HTML. Most of the time you'll probably just want to generate the HTML explicitly, but sometimes it's handy to use the functions.
<% if cgi.has_key?('foo') then %>
I also see that you've set parameter foo to <%=cgi['foo']%>. Thanks.
<% end %>
<p>
<%= cgi.a('hello.rhtml?foo=bar') { "Reload" } %>
</BODY>
</HTML>
Will produce just
and when the client clicks the link, the conditional block will be executed:
I also see that you've set parameter foo to bar. Thanks.
Reload
Finally, we increment the session's visits value before we finish.
<% sess['visits'] += 1 %>
OK. At this point you know the basic Ruby language, can connect to a database from the Ed Lab terminal, have learned about templates, and understand the basics behind ERB and the CGI module - including the session and cgi objects that allow us to produce dynamic pages and maintain state.
We'll pull this all together in the next piece where we will use Ruby to access your class project database from an ERB template.