From c1e0dfbf0ba0ade42299c3bc163e01b1221ac9d7 Mon Sep 17 00:00:00 2001 From: Nicholas Kariniemi Date: Fri, 3 Jul 2015 23:16:53 +0300 Subject: [PATCH] Switch to Datomic for storage (not persistent) - Still not taking advantage of Datomic features but using it as a simple dump for Clojure data structures. --- database_schema.edn | 53 +++++++++++++++++++++++++ project.clj | 5 ++- src/clj/grub/core.clj | 19 ++++----- src/clj/grub/db.clj | 85 +++++++++++++++++++++++++++++------------ src/cljc/grub/util.cljc | 18 ++++----- 5 files changed, 135 insertions(+), 45 deletions(-) create mode 100644 database_schema.edn diff --git a/database_schema.edn b/database_schema.edn new file mode 100644 index 0000000..68d89b7 --- /dev/null +++ b/database_schema.edn @@ -0,0 +1,53 @@ +[ + ;; grubs + {:db/id #db/id[:db.part/db] + :db/ident :grub/id + :db/valueType :db.type/keyword + :db/cardinality :db.cardinality/one + :db/unique :db.unique/identity + :db/doc "Grub ID" + :db.install/_attribute :db.part/db} + {:db/id #db/id[:db.part/db] + :db/ident :grub/text + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one + :db/fulltext true + :db/doc "The text of a grub item e.g. '3 apples'" + :db.install/_attribute :db.part/db} + {:db/id #db/id[:db.part/db] + :db/ident :grub/completed + :db/valueType :db.type/boolean + :db/cardinality :db.cardinality/one + :db/doc "The status of a grub item (completed or not completed)" + :db.install/_attribute :db.part/db} + + ;; recipes + {:db/id #db/id[:db.part/db] + :db/ident :recipe/id + :db/valueType :db.type/keyword + :db/cardinality :db.cardinality/one + :db/unique :db.unique/identity + :db/doc "Recipe ID" + :db.install/_attribute :db.part/db} + {:db/id #db/id[:db.part/db] + :db/ident :recipe/name + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one + :db/fulltext true + :db/doc "Recipe name" + :db.install/_attribute :db.part/db} + {:db/id #db/id[:db.part/db] + :db/ident :recipe/grubs + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one + :db/fulltext true + :db/doc "Recipe ingredients" + :db.install/_attribute :db.part/db} + {:db/id #db/id[:db.part/db] + :db/ident :recipe/directions + :db/valueType :db.type/string + :db/cardinality :db.cardinality/one + :db/fulltext true + :db/doc "Directions for making a recipe" + :db.install/_attribute :db.part/db} + ] diff --git a/project.clj b/project.clj index 51938d1..306e6f2 100644 --- a/project.clj +++ b/project.clj @@ -16,7 +16,10 @@ [sablono "0.3.4"] [cljs-uuid "0.0.4"] [com.cognitect/transit-clj "0.8.275"] - [com.cognitect/transit-cljs "0.8.220"]] + [com.cognitect/transit-cljs "0.8.220"] + [com.datomic/datomic-pro "0.9.5173" :exclusions [com.fasterxml.jackson.core/jackson-annotations]]] + :repositories {"my.datomic.com" {:url "https://my.datomic.com/repo" + :creds :gpg}} :profiles {:uberjar {:aot :all} :dev {:source-paths ["dev"] :dependencies [[org.clojure/tools.namespace "0.2.10"] diff --git a/src/clj/grub/core.clj b/src/clj/grub/core.clj index 9697d86..d29a527 100644 --- a/src/clj/grub/core.clj +++ b/src/clj/grub/core.clj @@ -39,8 +39,7 @@ (def prod-system {:index prod-index-page - :db-name "grub" - :db nil + :database-uri "datomic:mem://grub" :db-conn nil :port 3000 :stop-server nil @@ -48,8 +47,7 @@ (def dev-system {:index dev-index-page - :db-name "grub-dev" - :db nil + :database-uri "datomic:mem://grub" :db-conn nil :port 3000 :stop-server nil @@ -92,22 +90,21 @@ (handle-websocket states new-states-pub) (wrap-bounce-favicon))) -(defn start [{:keys [port db-name states] :as system}] - (let [{:keys [db conn]} (db/connect db-name) +(defn start [{:keys [port database-uri states] :as system}] + (let [db-conn (db/connect database-uri) new-states (chan) new-states-pub (a/pub new-states (fn [_] :new-state)) - db-state (db/get-current-state db) + db-state (db/get-current-state db-conn) _ (reset! states (state/new-states (if db-state db-state state/empty-state))) stop-server (httpkit/run-server (make-handler system new-states-pub) {:port port})] (add-watch states :db (fn [_ _ old new] (when-not (= old new) (let [new-state (state/get-latest new)] (a/put! new-states new-state) - (db/update-db! db new-state))))) + (db/update-db! db-conn new-state))))) (println "Started server on localhost:" port) (assoc system - :db db - :db-conn conn + :db-conn db-conn :stop-server stop-server :states states))) @@ -149,7 +146,7 @@ (println "options:" options) (cond (:help options) (exit 0 (usage summary)) - (not= (count arguments) 1) (exit 1 (usage summary)) + (not= (count arguments) 2) (exit 1 (usage summary)) errors (exit 1 (error-msg errors))) (case (first arguments) "development" (start (merge dev-system options)) diff --git a/src/clj/grub/db.clj b/src/clj/grub/db.clj index 9f2e5ec..cc58b68 100644 --- a/src/clj/grub/db.clj +++ b/src/clj/grub/db.clj @@ -1,34 +1,71 @@ (ns grub.db - (:require [datomic.api :as d :refer [q db]] - clojure.pprint - [monger.core :as m] - [monger.collection :as mc] - [clojure.core.async :as a :refer [! chan go]])) + (:require [datomic.api :as d] + [clojure.core.async :as a :refer [! chan go]] + [grub.util :as util])) -;(def uri "datomic:mem://seattle") -;(d/create-database uri) +(def schema-tx (read-string (slurp "database_schema.edn"))) +(defn create-db [uri] + (d/create-database uri) + (let [conn (d/connect uri)] + @(d/transact conn schema-tx))) -(def collection "grub-lists") +(defn connect [uri] + (create-db uri) + (let [conn (d/connect uri)] + (println "Connected to datomic at " uri) + conn)) -(defn clear-all [db] - (mc/drop db collection)) +(defn map-keys [key-maps coll] + (reduce (fn [new-coll [key new-key]] (assoc new-coll new-key (get coll key))) {} key-maps)) -(defn update-db! [db state] - (mc/drop db collection) - (mc/insert db collection state)) +(defn get-current-state [conn] + (let [db (d/db conn) + get-entity (fn [[id]] (d/touch (d/entity db id))) + grub-ids (d/q '[:find ?g :where [?g :grub/id]] (d/db conn)) + map-grub-keys #(map-keys {:grub/id :id :grub/text :text :grub/completed :completed} %) + grubs (->> grub-ids + (map (comp map-grub-keys get-entity)) + vec + (util/map-by-key :id)) + recipe-ids (d/q '[:find ?r :where [?r :recipe/id]] (d/db conn)) + map-recipe-keys #(map-keys {:recipe/id :id :recipe/name :name :recipe/grubs :grubs :recipe/directions :directions} %) + recipes (->> recipe-ids + (map (comp map-recipe-keys get-entity)) + vec + (util/map-by-key :id))] + {:grubs grubs + :recipes recipes})) -(defn get-current-state [db] - (let [state (first (mc/find-maps db collection))] - (when state - (dissoc state :_id)))) +(defn grub-tx [grub] + [{:db/id (d/tempid :db.part/user) + :grub/id (:id grub) + :grub/text (:text grub) + :grub/completed (:completed grub)}]) -(defn connect [db-name] - (let [conn (m/connect) - db (m/get-db conn db-name)] - (println "Connected to mongo at localhost:" db-name) - {:conn conn - :db db})) +(defn recipe-tx [recipe] + [{:db/id (d/tempid :db.part/user) + :recipe/id (:id recipe) + :recipe/name (:name recipe) + :recipe/grubs (:grubs recipe) + :recipe/directions (:directions recipe)}]) + +(defn update-db! [conn state] + (let [grubs-tx (->> state + :grubs + (vals) + (map grub-tx) + (flatten) + (vec)) + recipes-tx (->> state + :recipes + (vals) + (map recipe-tx) + (flatten) + (vec)) + tx (into grubs-tx recipes-tx)] + @(d/transact conn tx))) (defn disconnect [conn] - (m/disconnect conn)) + (d/release conn)) + diff --git a/src/cljc/grub/util.cljc b/src/cljc/grub/util.cljc index ce15f94..b0f516a 100644 --- a/src/cljc/grub/util.cljc +++ b/src/cljc/grub/util.cljc @@ -1,8 +1,8 @@ (ns grub.util - (:require #+clj [clojure.core.async :as a :refer [! chan go]] - #+cljs [cljs.core.async :as a :refer [! chan]]) - #+cljs (:require-macros [grub.macros :refer [log logs]] - [cljs.core.async.macros :refer [go]])) + (:require #?(:clj [clojure.core.async :as a :refer [! chan go]] + :cljs [cljs.core.async :as a :refer [! chan]])) + #?(:cljs (:require-macros [grub.macros :refer [log logs]] + [cljs.core.async.macros :refer [go]]))) (defn map-by-key [key coll] (->> coll @@ -11,11 +11,11 @@ (defn printer [] (let [in (chan)] - (go (loop [] + (go (loop [] (when-let [msg (