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:
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]
|
|
|