This is an implementation of Ohm in Common Lisp with some inspiration from Crane and datafly.
Ohm is an object-hash mapping for Redis.
First of all, Redis must be up and running. You can start Redis with the command line
$ redis-server
Then load CL-OHM
:
(ql:quickload :cl-ohm)
Without configuration Redis runs on localhost:6379
. If you’re using a different host or port you have to
configure CL-OHM
. For example, if Redis is running on 198.162.55.12
on port 12455
than you must setup
CL-OHM like this:
(ohm:setup-redis-connection :host #(198 162 55 12) :port 12455)
Use ohm:define-ohm-model
to specify your models.
(ohm:define-ohm-model person ()
:attributes ((first-name :indexp t)
(last-name :indexp t)
(email :uniquep t)))
You can create new persisted objects with CREATE
:
(ohm:create 'person :first-name "John" :last-name "McCarthy")
Attributes are setfable like ordinary objects slots (Note: if you don’t provide readers or writers for an attribute, an accessor will be created) but has to be explicitly saved to be persisted.
(ohm:create 'person :first-name "Bill")
(setf (first-name *) "William")
(ohm:save **)
When you know an object’s ID then you can load it with filter-id
(ohm:filter-id 'person "5")
;;; or
(ohm:filter-id 'person 5)
For each attribute marked with :INDEXP
and index gets created. With this index it is possible to load
objects by their values.
(ohm:filter 'person :first-name "Bill")
This load all objects with first-name=Bill
. Indexed attributes can be combined in FILTER
.
(ohm:filter 'person :first-name "Bill" :last-name "Miller")
If you omit any attribute specifiers from FILTER
than all objects for the given type are retrieved.
(ohm:filter 'person)
Each attribute marked as :UNIQUEP
must be unique for all instances of a given model. Considering the
person
model from above this means two instances cannot have the same email
. :UNIQUEP
also creates an
index, query-able with FILTER-WITH
.
(ohm:filter-with 'person :email "e@example.org")
This load the person
object with email=e@example.org
Counters let you count atomically.
(ohm:define-ohm-model candidate (person)
:counters (votes))
(let ((candidate (create 'candidate :first-name "Bill")))
(ohm:incr (votes candidate))
(ohm:incr (votes candidate))
(ohm:counter (votes candidate)) ;=> 2
(ohm:decr (votes candidate) 2)
(ohm:counter (votes candidate))) ;=> 0
Each model can define sets or lists as attributes. Sets and lists can hold other persisted objects defined by
DEFINE-OHM-MODEL
. Therefore you most provide the set’s or list’s element-type.
(ohm:define-ohm-model tag ()
:attributes ((name :indexp t)))
(ohm:define-ohm-model post ()
:lists ((authors :element-type person))
:sets ((tags :element-type tag)))
CL-OHM
persisted objects are internally stored in sets.
(ohm:create 'person :first-name "Donald" :last-name "Duck")
(ohm:filter 'person) ;=> #<CL-OHM::OHM-SET {1009FAB643}>
This lets you combine the FITLER
function with set operations.
Creating some test data:
(ohm:create 'person :first-name "Donald" :last-name "Duck")
(ohm:create 'person :first-name "Daisy" :last-name "Duck")
(ohm:create 'person :first-name "Gladstone" :last-name "Gander")
Creating the union of persons named Duck and persons named Gander:
(ohm:elements (ohm:union (ohm:filter 'person :last-name "Duck")
(ohm:filter 'person :last-name "Gander")))
Use EXCEPT
to exclude objects with specific properties. Say, exclude persons named Gander from all persons:
(ohm:elements (ohm:except (ohm:filter 'person) ; all persons
(ohm:filter 'person :last-name "Gander")))
Use COMBINE
to limit the resulting set. Say, all persons with last name Duck and first name Donald:
(ohm:elements (ohm:combine (ohm:filter 'person :last-name "Duck")
(ohm:filter 'person :first-name "Donald")))
Sets, lists and counters are stored implicitly after their mutation. If you change normal attributes (with
SETF
) then those objects have to be persisted with SAVE
.
See CL-OHM HTML Documentation.
CL-OHM uses FiveAM for testing. Please installed it with
(ql:quickload :fiveam)
Then you can run the test through ASDF:
(asdf:test-system :cl-ohm)