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))) ;=> 0Each 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)