Using OpenFootball-Glorp for illustration, this post is the first in a series on mapping an existing normalized database schema and other fun Glorp stuff. As usual, I'm using SQLite for the database.
Consider the tables GROUPS and TEAMS.
CREATE TABLE IF NOT EXISTS "groups" (
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
"event_id" integer NOT NULL,
"title" varchar NOT NULL,
"pos" integer NOT NULL,
"created_at" datetime NOT NULL,
"updated_at" datetime NOT NULL
);
CREATE TABLE IF NOT EXISTS "teams" (
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL,
"key" varchar NOT NULL,
"title" varchar NOT NULL,
-- many other columns omitted for now --
"created_at" datetime NOT NULL,
"updated_at" datetime NOT NULL
);
As it happens, every table in OpenFootball has columns "id", "created_at" and "updated_at", where "id" is that table's primary key. Let's take advantage of Smalltalk's inheritance and class hierarchy to map these columns and tables:
Object subclass: #OFObject
instanceVariableNames: 'pid createdAt updatedAt'
classVariableNames: ''
package: 'OpenFootball'
"Maps to GROUPS."
OFObject subclass: #OFGroup
instanceVariableNames: 'eventId title pos'
classVariableNames: ''
package: 'OpenFootball'
"Maps to TEAMS."
OFObject subclass: #OFTeam
instanceVariableNames: 'key title'
classVariableNames: ''
package: 'OpenFootball'
By convention, the Glorp mapping is encapsulated in the class OFDescriptor, which has these supporting methods:
virtualClassModelForOFObject: aClassModel
aClassModel newAttributeNamed: #pid type: Integer.
aClassModel newAttributeNamed: #createdAt type: DateAndTime.
aClassModel newAttributeNamed: #updatedAt type: DateAndTime.
virtualDescriptorForOFObject: aDescriptor with: aTable
(aDescriptor newMapping: DirectMapping)
from: #pid
to: (aTable fieldNamed: 'id'). "This is the primary key mapping."
(aDescriptor newMapping: DirectMapping)
from: #createdAt
type: DateAndTime
to: (aTable fieldNamed: 'created_at').
(aDescriptor newMapping: DirectMapping)
from: #updatedAt
type: DateAndTime
to: (aTable fieldNamed: 'updated_at').
virtualTableForOFObject: aTable
(aTable createFieldNamed: 'id' type: platform serial) bePrimaryKey.
aTable createFieldNamed: 'created_at' type: platform datetime.
aTable createFieldNamed: 'updated_at' type: platform datetime.
The mapping for OFGroup is as follows:
classModelForOFGroup: aClassModel
self virtualClassModelForOFObject: aClassModel.
aClassModel newAttributeNamed: #eventId type: Integer.
aClassModel newAttributeNamed: #title type: String.
aClassModel newAttributeNamed: #pos type: Integer.
descriptorForOFGroup: aDescriptor
| t |
t := self tableNamed: 'GROUPS'.
aDescriptor table: t.
self virtualDescriptorForOFObject: aDescriptor with: t.
(aDescriptor newMapping: DirectMapping)
from: #eventId
type: Integer
to: (t fieldNamed: 'event_id').
(aDescriptor newMapping: DirectMapping)
from: #title
type: String
to: (t fieldNamed: 'title').
(aDescriptor newMapping: DirectMapping)
from: #pos
type: Integer
to: (t fieldNamed: 'pos'.
tableForGROUPS: aTable
self virtualTableForOFObject: aTable.
aTable createFieldNamed: 'event_id' type: platform integer.
aTable createFieldNamed: 'title' type: platform varchar.
aTable createFieldNamed: 'pos' type: platform integer.
The mapping for OFTeam is similar and I've not shown it here for brevity.
To round out the scene setting, OFDatabase, the "database interface" class, has class-side convenience methods to run snippets like so:
OFDatabase
dbFileName: 'wc2018.db'
evaluate: [ :db |
db session read: OFGroup ]
To be continued...
Tags: Glorp, SQLite