A Brief, "Postmodern" Shout-out

Tagged as LISP, Personal, Programming

Written on 2010-04-12 01:11:48

Things have been crazy lately but I'm not here to give a full update. I will say that there's been good with the bad, family, friends and supporters all the while and that the bad is mostly the usual bureaucratic and financial troubles that are just a part of life. I'm trying to post more regularly. Today is a brief programming post.

This semester I've been taking a database course and we're building a small, silly webapp as the final project. The course uses SQL+PHP and I asked my professor if he wouldn't mind if I used SQL+Common Lisp. He accepted and so I've been using the Postmodern library for Common Lisp to talk to my Postgresql database. Postmodern has been really nice to use so far but there's one thing that I had a little trouble with that I'd like to document here.

Generally, if you're writing classes in Lisp you're using CLOS and an example might be something like this:
(defclass user ()
((username
:initarg :username :reader :username)
(password
:initarg :password :reader :password)
(salt
:initarg :salt :reader :salt)
(email
:initarg :email :reader :email)
(first-name
:initarg :first-name :reader :first-name)
(last-name
:initarg :last-name :reader :last-name)
(zip
:initarg :zip :reader :zip)))

Postmodern has a nice method for interacting with the database via class definitions that it coins "Database Access Objects". Note that DAOs neither are nor attempt to be a full ORM solution, a very sane decision in my humble and inexperienced opinion. Anyway, to make a normal class into a DAO class is easy, just do this:
(defclass user ()
((username
:col-type string
:initarg :username :reader :username)
(password
:col-type string
:initarg :password :reader :password)
(salt
:col-type string
:initarg :salt :reader :salt)
(email
:col-type string
:initarg :email :reader :email)
(first-name
:col-type string
:initarg :first-name :reader :first-name)
(last-name
:col-type string
:initarg :last-name :reader :last-name)
(zip
:col-type integer
:initarg :zip :reader :zip))
(:metaclass dao-class)
(:keys username))

All you have to do is add col-types to each slot so the system knows what type is stored in the database rows, list the components of the primary key and declare it a member of the dao-class metaclass. With that done, you can easily work with CLOS objects and fairly seamlessly select, update, delete or instantiate+insert them into the database. Creating the table itself can be done as follows: (execute (dao-table-definition 'user)). However, this is really intended as a shortcut for cases where you have a simple table definition. Say you wanted to allowed users to own collections of things, maybe collectible cards, and track those in the database as well. You ought to have foreign key constraints on the database so that collections couldn't be owned by users that didn't exist or consist of cards that didn't exist or were made up.

In the case where foreign key constraints are desired or other more complex checks should be made, the preferred method is to write a deftable definition in addition to the class and then create the table with (create-table 'class) or (create-all-tables) if you have several tables. This would make for nasty code duplication since you'd still need a dao-object class to interact with the tables as nicely as possible. Thankfully, there's a macro to clear the situation up and import the simple parts from your dao-class specification. A possible deftable for the collection class might look like this:
(deftable collection
(!dao-def) ;; Import the existing info from the dao-class definition.
(!foreign 'user 'username) ;; Ensure that the username that owns the collection exists in our user table.
;; Ensure that each card in a collection has a name and edition corresponding to a card in our database.
(!foreign 'card '(card-name card-edition)
'(name edition)))

Of course, if your tables are already created and you just want to access them or you want to create them at the psql prompt, you don't care about any of this. Hmm...I guess that's supposed to go at the top. Anyway, a more careful and thorough reading of the documentation would've shown me this but examples are nice and here one is in case anyone googles around for it like I did. As far as I can tell, this is the preferred current approach for table creation. Corrections welcome and thanks to Marijn Haverbeke for writing postmodern. It's been wonderful so far.
comments powered by Disqus

Unless otherwise credited all material Creative Commons License by Brit Butler