Saturday, February 10, 2007

A couple of simple Rails techniques

The first is to do with making it straightforward to always populate some fields in database tables. I prefer to have most database tables have these two time fields: last_updated_at, and created_at. What you want to do is that last_update_at field is updated every time the database row is updated, and created_at is populated with the current time at the time the row is inserted.

I created a mixin "Database::Table" that defines the methods "#before_create" and "#before_save" to have the desired behaviour. Now, in your ActiveRecord classes, include this mixin, and you're done. Here's the code.


module Database

# This classes updates the timestamps on the database tables, and forces
# all subclassed tables to have created_at and last_updated_at tables
module Table

def before_create
self.created_at = Time.now
self.last_updated_at = Time.now
end

def before_save
self.last_updated_at = Time.now
end

end

end


And the code that uses it:


require "rubygems"
require "activerecord"
require "database/table"

class User < ActiveRecord::Base
include Database::Table
# code...
end


The second technique is to use tables that have the "id" field to be something other than an integer -- the default type. We use another mixin for this: "Database::UuidBase."

require 'database/table'

module Database

# This class allows subclasses to use a guid/uuid based id
# instead of the sequential id used otherwise
module UuidBase

include Database::Table

def before_create
super
self.id = uuid if self.id.nil?
end

def uuid
# user your favorite library to generate a UUID
end

end
end

You now include this mixin into your class to get both the behaviours. There are several reasons why one may want to do this.

Often there is a unique id for a table that we need to be a string, and not an integer. If it is a part of all or most of your search queries and is unique for every row, you can avoid having an additional index by replacing the default "id" column with this string.

If you use an integer field that gets auto_incremented, and you need access to this field after an insert, you have to typically load the row from the database again. This means you need to do a search, and you cannot search on the primary key -- "id." You therefore have to rely on a secondary index -- assuming you have a secondary index. Howevery, by populating a unique number _before_ it is saved into the database, you have access to the "id" field after the insert without the need to reload it from the database.