cl-ohm

2017-06-30

file:https://travis-ci.org/rudolfochrist/cl-ohm.svg?branch=master file:https://coveralls.io/repos/github/rudolfochrist/cl-ohm/badge.svg?branch=master

This is an implementation of Ohm in Common Lisp with some inspiration from Crane and datafly.

Ohm is an object-hash mapping for Redis.

Usage

Starting and connecting 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)

Mapping objects to Redis

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 **)

Loading objects from the data store

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)

Indexes

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)

Unique values

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

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

Sets and Lists

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.

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")))

Persistence Strategy

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.

API Documentation

See CL-OHM HTML Documentation.

Running the tests

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)

License

MIT Copyright (c) 2016 Sebastian Christ