Fridge is a mapping between Common Lisp classes/objects and PostgreSQL.
The idea behind Fridge, is that you can infer most relations from the database itself. During the useage of the system, a small set of the database is replicated in the QuickStore of Fridge. This quickstore allows you to easily access the objects without unecessary accessing of the database. Although Fridge is far from complete, it is assumed to be useable.
Fridge has been developed under SBCL and uses closer-mop
. It uses some minor things which may be SBCL-specific.
This howto is not fully complete. This library is expected to be extended with neccessary content as it is used more often.
If you follow these simple conventions, the system will create a database-model which you can readily use.
CREATE TABLE your_class_name ( id SERIAL PRIMARY KEY, first_variable text, second_variable text, linked_class_id INTEGER, other_linked_class_ids INTEGER );
CREATE TABLE linked_class ( id SERIAL PRIMARY KEY, some_number INTEGER );
CREATE TABLE other_linked_class ( id SERIAL PRIMARY KEY, some_string text );
in which the names of the classes must be written in that form (an underscore instead of a dash). and the names of the columns matter in the following way:
- The
id
of the class is named with the slotid
and is auto-generated (or you’ll have to specify it each creation of a new object) - The slots that link to another class are named
class_name_id
if there is only one object referring to a certain id, orclass_name_ids
if there are multiple objects referring to that id.
The objects can then be loaded by using the CLOS way of loading instances by specifying both their superclass and their metaclass:
(use-package :fridge)
(defclass your-class-name (dbi-class)
;; no need to specify slots if you don't need anything besides the
;; slots and links
()
;; you can use symbol to denote that all created slots should be
;; part of the package, to which `my-package:some-internal-symbol'
;; belongs. If this is not specified, the package of
;; `your-class-name' is used. This is generally safe.
(:package my-package:some-internal-symbol)
(:metaclass dbi-metaclass))
(defclass linked-class (dbi-class)
()
(:metaclass dbi-metaclass))
(defclass other-linked-class (dbi-class)
()
(:metaclass dbi-metaclass))
So, which methods and slots have you gained by this? Let’s hop over each of the classes.
Some support is shared through each of classes.
- a slot for each column name, in its interned form. Being the slots:
(id first-variable second-variable linked-class-id other-linked-class-ids)
- for each none-linked column, an accessor is created. Being the accessors:
(id first-variable second-variable)
- for each none-linked column, the slot accepts initarg, resembling the name of the slot as a keyword argument.
Objects of this class are dependent on the definitions given in linked-class
and other-linked-class
. This is so because your-class-name
links to those classes. The given support is:
- for each linked column, an accessor is created to get/set the linked object. Being the accessors:
(linked-class other-linked-class)
. Note that the -id part is removed
Linked class is supposed to have one element of your-class-name
. This is so because the linking column name ended in -id.
- linked-class gets a slot with an accessor which returns or sets one object. The accessor is named as the class-name it links to. Being
your-class-name
in this example.
Other linked class is supposed to have multiple elements of your-class-name
. This is so because the linking column name ended in -ids.
other-linked-class
gets a slot with an accessor which returns or sets a list of objects. The accessor is named as theclass-name
it links to. Beingyour-class-name
in this example.
Saving and loading happens through separate methods.
You use (load-instance 'your-class-name :id your-id)
to load one object from the database. (load-instances 'your-class-name :first-variable 10)
can be used to fetch the list of objects that have 10 as their first-variable
value (eg: the value in the column first_variable
).
Only loaded-object
is loaded in that case. You can transparently refer to the objects in the database and link to them through the created accessors.
When you use validations, objects will only store of the requested section of objects their validation succeeds.
You can use (save object) to store a single object. This is the basic building block of saving. As this method is fairly cumbersome to use, some helpers have been made to make your life easier:
save-quickstore
: You use(save-quickstore)
to save all elements in the quickstore (which contains all objects which you have loaded), this is roughly equivalent to all objects you accessed.save-objects
: You can use(save-objects list-of-objects)
to store all objects in the given list.complete-ring
: You can use(complete-ring object)
to get a list of all objects that link to, or are linked from the given object. This can be used in combination with save-objects to save everything a certain object links to. This will fetch all objects, you may be more interested in using complete-stored-ring with respect to saving.complete-stored-ring
: You can call(complete-stored-ring object)
to get all the objects which link to, or are linked from the given object (and all those that link from there on (keep adding objects that are linked to/from until you can find no object that somehow links to object)) and which are stored in the quickstore. This will not do database operations to fetch the classes which need to be linked to.
It is assumed that you will use (save-objects (complete-stored-ring object))
most. If the object hasn’t been changed internally (but only has been fetched to inspect its values), the quickstore will not query the database to store that certain object.
When you use the linking as specified above, there is a master and an obedient class at play.
The obedient class has an extra slot, which links to the master’s id. In our previous example your-class-name
was the obedient class for both linked-class
and other-linked-class
. This difference is used in a way to handle the storing and removal of objects. When you disassign an object from a master class, that object will be removed from the database and it will be removed from the quickstore. In order for the object to be saved (and updated) again when saving the quickstore, you must re-add it to the quickstore. You can do this by calling (quickstore-again your-lost-object)
. Or you can call save on it immediately.