nwalsh.com on Rails

Volume 9, Issue 24; 23 Feb 2006; last modified 07 May 2013

A cursory look at using the Ruby on Rails framework to publish nwalsh.com.

The important thing in science is not so much to obtain new facts as to discover new ways of thinking about them.

William Bragg

I have a confession. I've never used any of the popular frameworks for constructing web sites. I've never written a page of PHP. Or JSP. Or ASP. I think I've installed Cocoon and Tomcat once or twice, but I never got around to using them. I've always just published baked (or mostly baked) HTML pages with maybe a few server-side includes and CGI scripts. Rolling my own back-end for this weblog was part of the fun.

So I was starting to feel a little left out. Or, perhaps more importantly, like I was missing any exposure to some pretty important technologies. And over the past few months, the buzz about Ruby on Rails had piqued my curiosity.

Before I stared this weblog, I used to publish much more actively on nwalsh.com. Now that I don't publish there as much, I've been thinking that it could use a bit of a face lift.

A little history

The nwalsh.com pages are built with a customization of DocBook called “Website”. The way Website works is that you author all of the pages independently and then you define the navigation by building a “layout file”. (There's some similarity to DITA map files here, actually.)

Having independent pages means that they can be rebuilt independently. If I edit the XML source for the Emacs page, only the Emacs page has to be rebuilt to bring the website up-to-date.

Well, mostly. If you look at the site, you'll see there is one apparently dynamic element, the menu system on the left hand side of the pages. Although it appears to be dynamic, it's actually static on each page. The expansion of tabs is an illusion. In reality, the layout file is used to determine where each page appears in the hierarchy and the correct “view” is rendered on the page.

This means that if you change the layout file (by adding a new page, for example), some (and in practice, all) of the pages have to be rebuilt. That's a drag and a dynamic framework could fix that. (JavaScript, etc. could fix it too, but I like the fact that the site doesn't require JavaScript, and it's been online since way before cross-browser JavaScript support was practical.)

Enter Rails

The idea of retooling nwalsh.com seemed to intersect with my interest in Rails, so I devoted a few of hours to hacking it over the past few evenings.

Through the magic of apt-get, it installed easily. And the O'Reilly tutorial quickly got me into the thick of it.

Plan of attack

I wanted to use the Rails framework to build the navigation menu dynamically. I decided to prototype this by using the layout file to build a database with a couple of tables that described the hierarchy. (I don't claim any great experience with database design, so if I made a pig's breakfast of it, so be it. Remember, this is just a little prototyping exercise.)

Conceptually, I divided up the navigation into categories (top-level entries in the navigation hierarchy), pages (second-level entries), and sub-pages (third-level entries, of which there are a few). Sub-pages turned out to be a bit of a PITA, so I invented a hack: if a page has a “parent page” then it's a sub-page, otherwise it's an ordinary page.

Once I had the databases built, I could access them in the Rails show method to build the navigation tree.

But what about the actual content? I didn't feel like going through the effort of actually getting that in the database, so I just hacked the stylesheets to produce HTML that included only the real content of the page, then I could slurp that up and dump it into the right place.

Implementation

After a little Googling around to learn enough SQL syntax to get myself going, it didn't take long to write the stylesheet that generated the code to build the database.

The result looks like this:

drop database if exists nwalshcom;
create database nwalshcom;
use nwalshcom;

create table pages (
id int(10) unsigned NOT NULL auto_increment,
category int(10) unsigned NOT NULL,
parent int(10) unsigned,
title varchar(255) NOT NULL default '',
uri varchar(255) NOT NULL default '',
primary key (id)
) engine=InnoDB default charset=latin1;

create table categories (
id int(10) unsigned NOT NULL auto_increment,
page int(10) unsigned NOT NULL,
name varchar(255) NOT NULL default '',
primary key (id)
) engine=InnoDB default charset=latin1;

insert into categories values (1,1,'HIDDEN');
insert into categories values (2,2,'Home');
insert into categories values (3,3,'DocBook');
--...etc., for the rest of the categories...

insert into pages values (1,1,null,'NO PAGE','');
insert into pages values (2,2,null,'Home','index.html');
insert into pages values (3,3,null,'DocBook','docbook/index.html');
insert into pages values (4,3,null,'Pub. Model','docbook/procdiagram/index.html');
insert into pages values (5,4,null,'XML','xml/index.html');
insert into pages values (6,4,null,'XSL Lint','xml/xslint/index.html');
--...
insert into pages values (10,5,null,'Presentations','docs/presentations/index.html');
insert into pages values (11,5,10,'DocTrain2005','docs/presentations/doctrain2005/index.html');
insert into pages values (12,5,10,'Extreme 2004','docs/presentations/extreme2004/index.html');
--...etc., for the rest of the pages...

Then all I had to do was write a show.rhtml file that used this. The result is about what you'd expect:

<html>
<head>
<title><%= @page.title %></title>
</head>
<body>

<table border="1" width="100%">
<tr>
<td align="left" valign="top" width="300">

<dl>
  <% @categories.each do |category| %>
    <% if (category.id > 1) %>
      <dt>
        <%= link_to category.name,
                    :controller => "page",
                    :action => "show",
                    :id => category.page %>
        <% if (@category_page and @page.category == category.id) %>
          <span>*</span>
        <% end %>
      </dt>
   …

Remarkably, especially when you consider that I haven't a clue about the Ruby programming language, it came together pretty quickly (not counting a bit of fumbling with some corner cases and off-by-one errors).

That's pretty obviously just a shell, but the navigation on that page really is dynamic.

Those pesky URIs

One obvious problem in doing something like this is dealing with all the current URIs. Turning all of the pages into things like http://nwalsh.com/page/show/10 might be fully functional, but it sure isn't very pretty.

I imagined that I'd simply have to setup a bunch of redirects or URI rewrites in the server's .htaccess file and live with the consequences.

Not so! Ruby on Rails has this marvelous facility called routing which allows you to establish the URIs that you want to use. That just transparently turns all the controller/action/id URIs into something reasonable.

Doubly cool was the fact that I could generate the correct routing table from the existing layout file. That produced something like this:

ActionController::Routing::Routes.draw do |map|
  # Custom routes 

  map.connect ':index',
              :controller => 'page',
              :action => 'show',
              :id => '2',
              :index => nil,
              :requirements => { :index => /^index.html$/ }

  map.connect 'docbook/:index',
              :controller => 'page',
              :action => 'show',
              :id => '3',
              :index => nil,
              :requirements => { :index => /^index.html$/ }

  map.connect 'docbook/procdiagram/:index',
              :controller => 'page',
              :action => 'show',
              :id => '4',
              :index => nil,
              :requirements => { :index => /^index.html$/ }

  …

It makes the URIs display “correctly” and it makes all the current links work. Pretty sweet.

Conclusions

Considering that I started knowing nothing about Ruby, essentially nothing about SQL, and with only the most meager experience with any sort of framework at all, the fact that I got from zero to working prototype in half a day, give or take, strikes me as pretty remarkable. (I'm not sure how I feel about the fact that I still know almost nothing about Ruby even after successfully building a working “application”. :-)

I'm not likely to take this project any further. I might someday give nwalsh.com a face lift, but I doubt that I'll do it with a framework like this.

That's not an indictment of Ruby on Rails or frameworks in general. It just seems like overkill for the task at hand. If I was building a more dynamic site, where users were creating or changing content, I'd be really excited about Ruby on Rails. (Hmm, maybe my Where in the World service, or even the blog comments framework would benefit from a framework approach?)

I'm also pretty repulsed by the grotesque mess of .rhtml files. That tangle of HTML and Ruby code stuck between magic “<%” and “%>”'s just feels wrong. (And it's common to all the frameworks, minor syntactic differences aside, as far as I can tell.) Not that I have a better suggestion just at the moment.

If you need a framework, Ruby on Rails gets a pretty enthusiastic “thumbs up” from me. I suppose other frameworks might as well, but I haven't tried them.

Comments

Have a look at the Kid templating system, http://kid.lesscode.org, it's the default with TurboGears and can optionally be used with Django (both python based frameworks).

I like the fact that it keeps code out of templates almost completely (there's an optional syntax to include code snippets, but I've personally never used it while evaluating the two named frameworks).

/Ian.

—Posted by Ian Phillips on 24 Feb 2006 @ 07:44 UTC #

I'm allergic to .rhtml as well (not least because it's a bit of a pain to get Emacs to be happy with it, and anything that hurts Emacs hurts me). Fortunately, Rails supports pluggable templating systems. XML::Builder is included as an alternative by default, and there's always Amrita if you want to do clean XHTML templates.

—Posted by Avdi on 24 Feb 2006 @ 03:29 UTC #

At work I've built a lot of PHP-based apps in the past, mostly for doing SQL queries into our internal bug-tracking and release-tracking databases, and building reports that combine the data that comes back. Basicallly a class of apps for which Rails seems like it would be a very good fit. So I've been interested in building a real app using Rails, but haven't yet identified a new problem in need of an new application. I guess I could try rewriting one of my existing PHP apps in Rails, would that I had time.

Anyway, I have done a bit of playing around with it, enough that it's had the nice side effect of making me learn a little Ruby. Which I'm very glad I did because it's made me realize how good Ruby itself is (outside of Rails) is a general language for writing utilities -- a class of apps that I've mostly used Perl for in the past, and some Python. It seems to have a lot of "hey, that's a really elegant approach" features -- like the "routing" feature you mention in this blog entry.

The first such utility that I built using Ruby was an XML-RPC client that builds an XML-RPC request, takes the XML-RPC response data and constructs a simple(r) XML document from it, then applies an XSLT stylesheet to that to convert it to HTML. It was an extremely simple app, but it was surprising to me how quick and easy it was to code it up in Ruby. Without knowing any Ruby at all initialy, after just a hour or two at most (and after slapping a CGI interface on the utility), I had a Ruby-based web app for querying my XML-RPC service and returning HTML-formatted results from it.

Anyway, Rails or no, I've definitely found a new friend in Ruby and I reckon I'll be using it a lot more going forward for doing quick development of other such utilities.

—Posted by MIchael(tm) Smith on 03 Mar 2006 @ 12:44 UTC #