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.

Comments

Popular posts from this blog

Ruby Sinatra web apps with background work threads

My Dad's work with Robert Oppenheimer and Edward Teller

Time and Attention Fragmentation in Our Digital Lives