A little Clojure wrapper for Datomic
I wrote yesterday about getting started with Datomic in a lein based project. Probably because I am not up to speed with Datomic idioms, a lot of the data boilerplate bugs me so I wrote a little wrapper to hide all of this from my view. Starting with a some code by Michael Nygard I saw on the Datomic newsgroup I wrapped creating database attributes and adding data to the data store. I formated the following code in a funky way to make it fit on this web page:
(ns datomic-test.core
(:use [datomic.api :as api]))
(defn attribute [id t c doc] ; by Michael Nygard
{:db/id (api/tempid :db.part/db)
:db/ident id
:db/valueType t
:db/cardinality c
:db/doc doc
:db.install/_attribute :db.part/db})
(defn string-singleton-attribute [conn id doc]
@(api/transact conn
[(attribute id
:db.type/string :db.cardinality/one doc)]))
(defn string-multiple-attribute [conn id doc]
@(api/transact conn
[(attribute id
:db.type/string :db.cardinality/many doc)]))
(defn long-singleton-attribute [conn id doc]
@(api/transact conn
[(attribute id
:db.type/long :db.cardinality/one doc)]))
(defn long-multiple-attribute [conn id doc]
@(api/transact conn
[(attribute id
:db.type/long :db.cardinality/many doc)]))
(defn do-tx-user [conn data-seq]
(let [data
(for [data data-seq]
(assoc data :db/id (api/tempid :db.part/user)))]
@(api/transact conn data)))
Michael's code wraps schema attribute definitions like I showed in the file data/schema.dtm in yesterday's blog. The function do-tx-user takes a seq of maps, adds the user database partition specification to each map, and runs a transaction. With this wrapper, I don't use a separate schema input data file anymore. Here is the example I showed yesterday using the wrapper:
(ns datomic-test.test.core
(:use [datomic-test.core])
(:use [clojure.test]))
(use '[datomic.api :only [q db] :as api])
(use 'clojure.pprint)
;;(def uri "datomic:free://localhost:4334//news")
(def uri "datomic:mem://news")
(api/create-database uri)
(def conn (api/connect uri))
;; create two singleton string attributes and a number
;; attribute and add them to the :db.part/db partition:
(string-singleton-attribute
conn :news/title "A news story's title")
(string-singleton-attribute
conn :news/url "A news story's URL")
(long-singleton-attribute
conn :news/reader-count "Number of readers")
;; add some data to the :db.part/user partition:
(do-tx-user conn
[{:news/title "Rain Today",
:news/url "http://test.com/news1",
:news/reader-count 11}
{:news/title "Sunshine tomorrow",
:news/url "http://test.com/news2",
:news/reader-count 8}])
(def results
(q '[:find ?n :where [?n :news/title]] (db conn)))
(println (count results))
(doseq [result results]
(let [id (first result)
entity (-> conn db (api/entity id))]
(println (:news/title entity) (:news/reader-count entity))))
Since I use many different tools, I sometimes like to figure out the subset of APIs, etc. that I need and wrap them in a form that is easier for me to remember and use. This may be a bad habit because I can end up permanently using a subset of tool functionality.