Print Logo

Porting an Existing Static Database-derived Web Site to Ruby on Rails




<<  <   >  >>

[Note that this article is an experiment in hacking a Rails app in to static HTML off of a single root directory. This was done with routes.rb and some parsing of the URL within Rails. This is interesting in its own right, so we are leaving it up; however, a much better way is to simply go with the Tao of Rails. Specifically, for the artpage view below, simply use a rewrite on the URL: ^/art([0-9]+)\.html$ /sysadmin/artpage/$1 .]

We have a site that uses a database to store articles, headers, footers, and other information. We decided to port the site to Ruby on Rails. For more on installing Ruby and Rails on Mac OS X, see our recent article. The first step was to export the old database:

srv-8:mcjr usr4$ sqlite3 ~/sysadmintoolsold/mcj.rsd
sqlite> .output db.txt
sqlite> .dump
sqlite> .quit

We ended up doing this repeatedly to ensure that there were no name conflicts, so made a script we could run every time we erased mcjr (our app):

srv-8:rubyarts usr4$ cat m.sh
rails --database=sqlite3 mcjr
cd ~/mcjr
rake db:create RAILS_ENV='development'
cp ~/rubyarts/db.txt ~/mcjr/
srv-8:rubyarts usr4$

We need to create a controller:

srv-8:mcjr usr4$ ruby script/generate controller Sysadmin
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/sysadmin
      exists  test/functional/
      create  test/unit/helpers/
      create  app/controllers/sysadmin_controller.rb
      create  test/functional/sysadmin_controller_test.rb
      create  app/helpers/sysadmin_helper.rb
      create  test/unit/helpers/sysadmin_helper_test.rb
srv-8:mcjr usr4$

This is what our database configuration looks like:

srv-8:app usr4$ cat config/database.yml
# SQLite version 3.x
#   gem install sqlite3-ruby (not necessary on OS X Leopard)
development:
  adapter: sqlite3
  database: db/development.sqlite3
  pool: 5
  timeout: 5000

test:
  adapter: sqlite3
  database: db/test.sqlite3
  pool: 5
  timeout: 5000

production:
  adapter: sqlite3
  database: db/production.sqlite3
  pool: 5
  timeout: 5000
srv-8:app usr4$

The articles are on multiple lines, which makes it kind of nasty to import and export. The easiest, at least that we could come up with, was to hack on the dump with Perl:

srv-8:rubyarts usr4$ cat mig.sh
perl -pi -e "s|cats|cats|g" ~/mcjr/db.txt
perl -pi -e "s|tims|tims|g" ~/mcjr/db.txt
perl -pi -e "s|\"tims\"|\"tims\"|g" ~/mcjr/db.txt
perl -pi -e "s|\(cat text|\(cat text|g" ~/mcjr/db.txt
perl -pi -e "s|\"journal\"|\"arts\"|g" ~/mcjr/db.txt
perl -pi -e "s|journal\(|arts\(|g" ~/mcjr/db.txt
perl -pi -e "s|types\(|classifications\(|g" ~/mcjr/db.txt
perl -pi -e "s|VALUES\(|VALUES\(NULL,|g" ~/mcjr/db.txt
perl -pi -e "s|people\(|peoples\(|g" ~/mcjr/db.txt
perl -pi -e "s|\"people\"|\"peoples\"|g" ~/mcjr/db.txt
perl -pi -e "s|times\(|tmes\(|g" ~/mcjr/db.txt
perl -pi -e "s|atme time,|atme time,|g" ~/mcjr/db.txt
perl -pi -e "s|time Text\)|tme Text\)|g" ~/mcjr/db.txt
perl -pi -e "s|classification Text,|classification Text,|g" ~/mcjr/db.txt
perl -pi -e "s|type Text\)|classification Text\)|ig" ~/mcjr/db.txt
perl -pi -e "s|\"types\" VALUES|\"classifications\" VALUES|g" ~/mcjr/db.txt
perl -pi -e 's|CREATE TABLE (.+)\((\w+) |CREATE TABLE $1\(id INTEGER PRIMARY 
KEY,$2 |g' ~/mcjr/db.txt
perl -pi -e "s|images\(id|imgs\(id|g" ~/mcjr/db.txt
perl -pi -e "s|\"images\" VALUES|\"imgs\" VALUES|g" ~/mcjr/db.txt
perl -pi -e "s|urls\(id|uresources\(id|g" ~/mcjr/db.txt
perl -pi -e "s|\"urls\" VALUES|\"uresources\" VALUES|g" ~/mcjr/db.txt
srv-8:rubyarts usr4$ 

This makes the old schema of MCJ somewhat happy with Rails. Here is the schema we ended up with:

srv-8:mcjr usr4$ cat db/schema.rb

ActiveRecord::Schema.define(:version => 0) do

  create_table "arts", :force => true do |t|
    t.text    "title"
    t.text    "classification"
    t.date    "date"
    t.time    "atme"
    t.text    "entry"
    t.integer "artnum"
    t.text    "realm"
  end

  create_table "cats", :force => true do |t|
    t.text    "cat"
    t.text    "u1"
    t.text    "u2"
    t.text    "u3"
    t.text    "u4"
    t.text    "u5"
    t.text    "u6"
    t.integer "artnum"
  end

  create_table "classifications", :force => true do |t|
    t.text "name"
    t.text "longtitle"
    t.text "shortitle"
    t.text "pagetitle"
    t.text "realm"
    t.text "client"
    t.text "matter"
    t.text "description"
    t.text "project"
  end

  create_table "imgs", :force => true do |t|
    t.text    "realm"
    t.integer "artnum"
    t.text    "tag"
    t.binary  "file"
    t.text    "alt"
    t.text    "label"
    t.text    "classification"
  end

  create_table "peoples", :force => true do |t|
    t.text    "realm"
    t.integer "artnum"
    t.text    "person"
  end

  create_table "places", :force => true do |t|
    t.text    "realm"
    t.integer "artnum"
    t.text    "place"
  end

  create_table "realms", :force => true do |t|
    t.text "name"
    t.text "head"
    t.text "mid"
    t.text "foot"
    t.text "longtitle"
    t.text "shortitle"
    t.text "pagetitle"
    t.text "rectcode"
    t.text "prop"
    t.text "style"
  end

  create_table "settings", :force => true do |t|
    t.text "parameter"
    t.text "value"
  end

  create_table "things", :force => true do |t|
    t.text    "realm"
    t.integer "artnum"
    t.text    "thing"
  end

  create_table "tims", :force => true do |t|
    t.float   "hours",  :limit => 2
    t.text    "u1"
    t.text    "u2"
    t.text    "u3"
    t.text    "u4"
    t.text    "u5"
    t.text    "u6"
    t.integer "artnum"
  end

  create_table "tmes", :force => true do |t|
    t.text    "realm"
    t.integer "artnum"
    t.text    "tme"
  end

  create_table "uresources", :force => true do |t|
    t.text    "realm"
    t.integer "artnum"
    t.text    "tag"
    t.binary  "file"
    t.text    "desc"
    t.text    "label"
    t.text    "classification"
  end

end

Generate the models (table objects) with:

srv-8:rubyarts usr4$ cat mod.sh
ruby script/generate model uresource
ruby script/generate model art
ruby script/generate model setting
ruby script/generate model people
ruby script/generate model place
ruby script/generate model build
ruby script/generate model cat
ruby script/generate model thing
ruby script/generate model tme
ruby script/generate model tim
ruby script/generate model img
ruby script/generate model realm
ruby script/generate model classification

srv-8:rubyarts usr4$

If there is a name conflict, an error like this will pop up:

The single-table inheritance mechanism failed to locate the subclass: 'early'. 
This error is raised because the column 'type' is reserved for storing the class 
in case of inheritance. Please rename this column if you didn't intend it to be 
used for storing the inheritance class or overwrite Journ.inheritance_column 
to use another column for that information.

To start the server with a different port number:

sudo ruby script/server -p 80

The first problem we had was that our routes were a bit difficult because all articles are in the root (just like on NetAdminTools). Here is how we set up config/routes.rb to distinguish these two:

  map.connect ':id.:format', :requirements => {:id => /part\d+/}, 
              :controller=>'Sysadmin', :action=>'printpage'
  map.connect ':id.:format', :requirements => {:id => /art\d+/}, 
              :controller=>'Sysadmin', :action=>'artpage'

Note how there are no slashes in the first map.connect parameter. The :requirements distinguish between print-nice articles and regular articles using a regular expression. Here is what the controller, Sysadmin looks like so far:

  class SysadminController < ApplicationController
    def printpage
      @realmrow = Realm.find(:all, :conditions=> ["name='sys'"])
      artNum = params[:id].gsub("part","")
      @artrow = Art.find(:all, :conditions=> ["artnum='" + artNum + "'"] )
    end
    def artpage
      @realmrow = Realm.find(:all, :conditions=> ["name='sys'"])
      artNum = params[:id].gsub("art","")
      @artrow = Art.find(:all, :conditions=> ["artnum='" + artNum + "'"] )
    end
  end

Here is a bit of the code that pulls out the entry from the DB in views\Sysadmin\artpage.html.erb:


<% for j in @artrow %> 
<%= j.entry %>
<% end %> 

Pretty much we can put the whole page in here and pick off the dynamic or DB derived code as we migrate from a static (but DB generated) site to a somewhat dynamic site with rails. Note that if you add an h after the =, anything returned will be converted to proper HTML from text. We don't want that in this case, but it is cool. Note that this was our first attempt at coding with Rails. Code these days is looking a lot better. This is just to illustrate the syntax highlighting:MacVim rocks:

macvim

There are tabs, even, so it means we can live without TextMate! We still have quite a bit to pull out of the database, but we are getting there. Very colorful along the way. Here is the output of the Mongrel server:

Processing SysadminController#artpage to html (for 127.0.0.1 at 2009-11-22 18:02:07) [GET]
  Parameters: {"id"=>"art1"}
  Realms Load (0.7ms)   SELECT * FROM "realms" WHERE (name='sys') 
  Journal Load (2.2ms)   SELECT * FROM "arts" WHERE (artnum='1') 
Rendering sysadmin/artpage
Completed in 13ms (View: 5, DB: 3) | 200 OK [http://www.sysadmintools.com/art1.html]





This article comes from NetAdminTools:
http://www.netadmintools.com/

The URL for this story is:
http://www.netadmintools.com/art619.html

Copyright 1997-2009 NetAdminTools.com. Read our Terms of Use.