Saturday, March 18, 2006

Book Reviews with Ruby On Rails

Late last year, I came across this ONLamp article on Ruby on Rails. I had heard of the scripting language Ruby before, while I was looking at moving to Python from Perl as my scripting language of choice. To be honest, Ruby the language did not (and still does not) interest me very much. I find it lacking in many features that is taken for granted in Perl, and to a lesser extent, Python. I also find the syntax of Ruby a little "unnatural", since it overloads various syntax notations that mean different things in Perl, which I am already used to.

So, had it not been for Ruby on Rails, I probably wouldn't have looked at Ruby ever again. However, Ruby on Rails (RoR) provides functionality that is too good to pass up merely because I dont like the underlying language. To put it briefly, RoR is a web-development toolkit in a box. You provide the underlying database schema, and RoR provides scripts that generate a bunch of files that will provide basic CRUD (Create, Retrieve, Update, Delete) functionality via a web browser. All without writing a single line of Ruby code!

Of course, what RoR generates is almost never going to be good enough for your customers, unless your customers have exceptionally low expectations, or unless the customer is you and you are looking for an alternative to doing straight SQL calls on the database. This is because the database (and by extension, RoR) does not know how its various tables are linked together. To make the RoR application look remotely like something from the real world, you will have to learn about how RoR works, and you will have to learn Ruby. So the TANSTAAFL (There A'int No Such Thing As A Free Lunch) rule applies here as well.

Luckily, I fell into the latter category. I needed a tool to write and populate book reviews into a database. These book reviews would then be rendered into web pages for my Amazon.com affiliate website.

But first, a little history. Sometime in 1996, I signed up for a free web page from Geocities, a company which has since been acquired by Yahoo!. I knew some basic HTML, actually above-average for the time, since I had recently finished writing a CGI program to allow people to send me database change request forms over the corporate intranet. Two sites I found particularly helpful were Joe Burn's HTMLGoodies (for HTML coding) and Phil Greenspun's photo.net (for CGI scripting).

Some time later, Amazon.com started selling books, and they attempted to increase their marketing presence by offering the Amazon affiliate program, and I happily signed up for that too. I figured that I would be particularly well suited for this sort of thing, since I read (a lot, mostly technical books), both out of choice (because I enjoy reading) and necessity (because my career requires me to keep abreast of the very rapid changes in my chosen field). I also figured that if I did get some commissions on clickthrough traffic, maybe I could buy a couple of books a year with that money, not much I know, but every little bit helps. So I wrote up some book reviews of what I had read, and put up a page with the links to Amazon's store.

However, I was writing the pages manually with a text editor, so remembering to keep each review in the same format and keeping the links consistent was laborious and time consuming. I also never actually made any money off the affiliate storefront. I soon found other, more interesting things to do, and the site languished for a while. I then redesigned the entire site to be link-less (ie, no links between pages in my site) site, so it would cut down on the manual labor involved in keeping links up-to-date. But the site as a result of the redesign was (and still is) ugly and almost un-navigable.

I have been planning another site redesign, this time as static pages generated off content from a database. So RoR looked ideal for building a simple tool that would let me enter book reviews and categorize them into various broad categories. I could write a Python script to read the database and generate a bunch of HTML pages which I could upload to the geocities server. So in effect, I was getting my free lunch - a web based tool to enter my data, written in Ruby by RoR, and a Python script (which I would write myself, and which was "free" too, since I already knew Python) to generate the pages and upload to the server.

So last week I set up a simple RoR application with two tables in the database - books and categories. A category can have many books within it. The process to get the basic CRUD functionality up took less than an hour. Here is the database schema:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
create table books (
    id bigint not null auto_increment,
    name varchar(255) not null,
    author varchar(255) not null,
    amazon_asin varchar(32) not null,
    review text not null,
    category_id bigint not null,
    primary key(id)
) type=InnoDB;
                                                                                
create table categories (
    id bigint not null auto_increment,
    category_name varchar(128) not null,
    primary key(id)
) type=InnoDB;

RoR is optimized for MySQL. My database of choice for personal work is PostgreSQL, but I could not make RoR work with it. Although I did not try very hard, all I did was download postgres-pr (the Ruby binding for PostgreSQL) and update the config/database.yml file to use PostgreSQL. I may have to learn more about RoR to hook it up with PostgreSQL.

The only other changes I had to make to link categories and books together in the RoR object model was to add the belongs_to and has_many declarations to the model classes:

1
2
3
4
5
6
7
8
9
# app/models/book.rb
class Book < ActiveRecord::Base
    belongs_to :category
end

# app/models/category.rb
class Category < ActiveRecord::Base
    has_many :book
end

To make the selection list of categories to show up in my Book edit form, I added this single line to the edit method in app/controllers/books_controller.rb to generate the complete list of categories.

1
2
3
4
5
def edit
    @book = Book.find(params[:id])
    # added this line
    @categories = Category.find_all
end

And this snippet to the views/books/edit.rhtml file to display a select list of all categories with the currently selected category highlighted.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# views/books/edit.rhtml
...
<%= render :partial => 'form' %>
# added this snippet here                                                                                
&;lt;p><b>Category:</b><br>
<select name="book[category_id]">
 <% @categories.each do |category| %>
     <option value="<%= category.id %>"
       <%= ' selected' if category.id == @book.category_id %>>
       <%= category.category_name %>
     </option>
 <% end %>
</select></p>
...

All these changes did not require me to gain a deep understanding of the RoR framework, I really extrapolated from Curt Hibbs's ONLamp article referenced above.

However, I have been looking at the features of RoR in more depth since I first saw the ONLamp article. I have even purchased the book "Agile Web Development with Rails" by Dave Thomas and David Hansson, and have read through it to understand how RoR works under the covers. I find RoR to be a very well constructed toolkit. A lot of thought has been put into the correct way to do things, such as using HTTP POST requests to trigger operations that update the database. It also automatically generates unit test code skeletons as it generates the code for controllers, views and models, so there is built in support for a test driven development environment. RoR has fewer artefacts than comparitive J2EE frameworks, since RoR relies on convention rather than configuration, and for most of the artefacts that it does need, it generates at least a base template which the developer can customize. Because RoR generates the base application, its developers typically have more time to spend writing code thats important to the customer rather than trying to configure the framework to work with the application. As a result, a RoR developer is more likely to develop a well-designed application with fewer bugs than a Java/J2EE developer.