diff --git a/.gitignore b/.gitignore index fcbc881..79b0447 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ pom.xml.asc .hg/ figwheel_server.log /resources/public/js +/out/ diff --git a/README.md b/README.md index dafe0c4..356d45a 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ even after the component using that query is un-rendered. (Thanks, [metasoarous] they sort-of worked in the older version. If you need to use those, just keep using the older version until those expressions are supported. - + ## Overview Posh gives you two functions to retrieve data from the database from @@ -307,7 +307,7 @@ one day explain further. ### Editable Label This component will show the text value -for any entity and attrib combo. There is an "edit" button that, when clicked, +for any entity and attrib combo. There is an "edit" button that, when clicked, creates an `:edit` entity that keeps track of the temporary text typed in the edit box. The "done" button resets the original value of the entity and attrib and deletes the `:edit` entity. The @@ -369,6 +369,22 @@ Datomic db over to DataScript. See our Gitter room for updates: https://gitter.im/mpdairy/posh +## Developing this library + +Start a Clojure REPL via your normal way -- `M-x cider-jack-in` for Emacs users. + +Start a CLJS REPL via `lein trampoline cljsbuild repl-listen` + +Files of interest: + +* posh.clj.datomic.clj - Clojure Datomic API +* posh.clj.datascript.clj - Clojure Datascript API +* posh.reagent - CLJS Datascript API + +### Running tests + +Run `lein test` from project root + ## License Copyright © 2015 Matt Parker diff --git a/project.clj b/project.clj index e12ba18..c1c7da8 100644 --- a/project.clj +++ b/project.clj @@ -1,22 +1,21 @@ -(defproject posh "0.5.6" +(defproject posh "0.5.7" :description "Luxuriously easy and powerful Reagent / Datascript front-end framework" :url "http://github.com/mpdairy/posh/" :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} - :dependencies [[org.clojure/clojure "1.7.0"] - [org.clojure/clojurescript "1.7.228"] - #_[datascript "0.15.0"] + :dependencies [[org.clojure/clojure "1.10.1"] + [org.clojure/clojurescript "1.10.520"] + #_[datascript "0.18.6"] #_[com.datomic/datomic-free "0.9.5407"] - [org.clojure/core.match "0.3.0-alpha4"]] + [org.clojure/core.match "0.3.0"]] :plugins [[lein-cljsbuild "1.1.3"]] - :cljsbuild { - :builds [ {:id "posh" + :profiles {:test {:dependencies [[datascript "0.18.6"]]}} + :cljsbuild {:builds [ {:id "posh" :source-paths ["src/"] :figwheel false :compiler {:main "posh.core" :asset-path "js" :output-to "resources/public/js/main.js" - :output-dir "resources/public/js"} } ] - } + :output-dir "resources/public/js"} } ]} :scm {:name "git" :url "https://github.com/mpdairy/posh"}) diff --git a/src/posh/clj/datascript.clj b/src/posh/clj/datascript.clj index f20b0ff..5a7cd8a 100644 --- a/src/posh/clj/datascript.clj +++ b/src/posh/clj/datascript.clj @@ -6,6 +6,7 @@ (def dcfg (let [dcfg {:db d/db :pull* d/pull + :pull-many d/pull-many :q d/q :filter d/filter :with d/with @@ -15,6 +16,6 @@ :conn? d/conn? :ratom rx/atom :make-reaction rx/make-reaction}] - (assoc dcfg :pull (partial base/safe-pull dcfg)))) + (assoc dcfg :pull (partial base/safe-pull dcfg)))) (base/add-plugin dcfg) diff --git a/src/posh/clj/datomic.clj b/src/posh/clj/datomic.clj index 473e2ad..aa3e8f4 100644 --- a/src/posh/clj/datomic.clj +++ b/src/posh/clj/datomic.clj @@ -20,6 +20,7 @@ (def dcfg (let [dcfg {:db d/db :pull* d/pull + :pull-many d/pull-many :q d/q :filter d/filter :with d/with @@ -29,6 +30,6 @@ :conn? conn? :ratom rx/atom :make-reaction rx/make-reaction}] - (assoc dcfg :pull (partial base/safe-pull dcfg)))) + (assoc dcfg :pull (partial base/safe-pull dcfg)))) (base/add-plugin dcfg) diff --git a/src/posh/core.cljc b/src/posh/core.cljc index 05158e7..3589a72 100644 --- a/src/posh/core.cljc +++ b/src/posh/core.cljc @@ -90,15 +90,15 @@ (let [storage-key [:filter-q query args] cached (get cache storage-key)] (assoc - (if cached + (if cached + posh-tree + (let [{:keys [analysis dbvarmap]} (u/update-q-with-dbvarmap posh-tree storage-key)] + (merge posh-tree - (let [{:keys [analysis dbvarmap]} (u/update-q-with-dbvarmap posh-tree storage-key)] - (merge - posh-tree - {:graph (graph/add-item-connect graph storage-key (vals dbvarmap)) - :cache (assoc cache storage-key - (u/filter-q-transform-analysis analysis))}))) - :return storage-key))) + {:graph (graph/add-item-connect graph storage-key (vals dbvarmap)) + :cache (assoc cache storage-key + (u/filter-q-transform-analysis analysis))}))) + :return storage-key))) ;; ================== queries ==================== @@ -107,28 +107,43 @@ (let [storage-key [:pull poshdb pull-pattern eid] cached (get cache storage-key)] (assoc - (if cached + (if cached + posh-tree + (let [analysis (merge + {:tx-t 0} + (u/update-pull posh-tree storage-key))] + (merge posh-tree - (let [analysis (merge - {:tx-t 0} - (u/update-pull posh-tree storage-key))] - (merge - posh-tree - {:graph (graph/add-item-connect graph storage-key [poshdb]) - :cache (assoc cache storage-key analysis)}))) - :return storage-key))) + {:graph (graph/add-item-connect graph storage-key [poshdb]) + :cache (assoc cache storage-key analysis)}))) + :return storage-key))) + +(defn add-pull-many [{:keys [graph cache dcfg conns conns-by-id retrieve] :as posh-tree} poshdb pull-pattern eids] + (let [storage-key [:pull-many poshdb pull-pattern eids] + cached (get cache storage-key)] + (assoc + (if cached + posh-tree + (let [analysis (merge + {:tx-t 0} + (u/update-pull-many posh-tree storage-key))] + (merge + posh-tree + {:graph (graph/add-item-connect graph storage-key [poshdb]) + :cache (assoc cache storage-key analysis)}))) + :return storage-key))) (defn add-q [{:keys [cache graph dcfg conns retrieve] :as posh-tree} query & args] (let [storage-key [:q query args] cached (get cache storage-key)] (assoc - (or cached - (let [{:keys [analysis dbvarmap]} (u/update-q-with-dbvarmap posh-tree storage-key)] - (merge - posh-tree - {:graph (graph/add-item-connect graph storage-key (vals dbvarmap)) - :cache (assoc cache storage-key analysis)}))) - :return storage-key))) + (or cached + (let [{:keys [analysis dbvarmap]} (u/update-q-with-dbvarmap posh-tree storage-key)] + (merge + posh-tree + {:graph (graph/add-item-connect graph storage-key (vals dbvarmap)) + :cache (assoc cache storage-key analysis)}))) + :return storage-key))) ;; ======================= remove items =================== @@ -229,5 +244,3 @@ {} txs)] (after-transact (assoc posh-tree :txs {}) conns-results))) - - diff --git a/src/posh/lib/pull_analyze.cljc b/src/posh/lib/pull_analyze.cljc index ad04483..bf07cd9 100644 --- a/src/posh/lib/pull_analyze.cljc +++ b/src/posh/lib/pull_analyze.cljc @@ -184,14 +184,14 @@ {:patterns {db-id (dm/reduce-patterns - (concat - (when (vector? ent-id) - [['_ (first ent-id) (second ent-id)]]) - (tx-pattern-for-pull - schema - prepped-pull-pattern - affected-datoms - false)))}}) + (concat + (when (vector? ent-id) + [['_ (first ent-id) (second ent-id)]]) + (tx-pattern-for-pull + schema + prepped-pull-pattern + affected-datoms + false)))}}) (when (some #{:ref-patterns} retrieve) {:ref-patterns {db-id @@ -201,19 +201,17 @@ prepped-pull-pattern affected-datoms true))}})))))))) - + (defn pull-many-analyze [dcfg retrieve {:keys [db schema db-id]} pull-pattern ent-ids] (when-not (empty? retrieve) (let [resolved-ent-ids (map #((:entid dcfg) db %) ent-ids) - affected-datoms - (map (fn [ent-id] (pull-affected-datoms (:pull dcfg) db pull-pattern ent-id)) - resolved-ent-ids)] + affected-datoms (pull-affected-datoms (:pull-many dcfg) db pull-pattern ent-ids)] (merge (when (some #{:results} retrieve) {:results affected-datoms}) (when (some #{:datoms :datoms-t} retrieve) (let [datoms (mapcat #(generate-affected-tx-datoms-for-pull schema %) - affected-datoms)] + affected-datoms)] (merge (when (some #{:datoms} retrieve) {:datoms {db-id datoms}}) @@ -233,5 +231,3 @@ (vec (cons (set resolved-ent-ids) (rest (ffirst patterns)))) (mapcat rest patterns)) (dm/reduce-patterns (apply concat patterns)))}}))))) - - diff --git a/src/posh/lib/q_analyze.cljc b/src/posh/lib/q_analyze.cljc index f453209..9f26125 100644 --- a/src/posh/lib/q_analyze.cljc +++ b/src/posh/lib/q_analyze.cljc @@ -49,7 +49,8 @@ (defn eav? [v] (and (vector? v) - (not (some coll? v)))) + (not (or (coll? (first v)) + (coll? (second v)))))) (defn wildcard? [s] (= s '_)) @@ -374,15 +375,58 @@ (apply merge)))) (defn split-datoms [datoms] - (->> (group-by first datoms) - (map (fn [[db-sym db-datoms]] - {db-sym - (map (comp vec rest) db-datoms)})) - (apply merge))) + (->> (group-by first datoms) + (map (fn [[db-sym db-datoms]] + {db-sym + (map (comp vec rest) db-datoms)})) + (apply merge))) -(defn resolve-any-idents [entid-fn db input-set] - (set (for [x input-set] - (if (coll? x) (entid-fn db x) x)))) +(defn- schema-ref? + "Returns whether attribute identified by k is of :db/valueType :db.type/ref" + [schema k] + (= :db.type/ref (:db/valueType (get schema k)))) + +(defn- indexes-of [e coll] (keep-indexed #(if (= e %2) %1) coll)) + +(defn- lookup-ref? + "Returns whether var-name is used as lookup-ref inside of query's :where clauses. + var-name - the symbolic variable name + where - coll of where clauses + schema - map of schemas with attribute names as keys + Returns boolean true or false" + [schema where var-name var-value] + (if-not (coll? var-value) + false + (loop [clause (first where) + remaining (rest where)] + (condp = (first (indexes-of var-name clause)) + 1 true + + ;; If datascript supported :db/valueTuple :db.type/tuple, could check that here + ;; instead of needing to scan every :where clause to ensure it's not a schema-ref + 3 (if (schema-ref? schema (nth clause 2)) + true + (if (seq remaining) + (recur (first remaining) (rest remaining)) + false)) + + (if (seq remaining) + (recur (first remaining) (rest remaining)) + false))))) + +(defn resolve-any-idents + "Given input-set from query, resolves any lookup-refs + Inputs: + entid-fn - Datomic/DS function to take lookup-ref & returns entid + db - value of DB + schemas - map with keys matching known schema attributes + where - where clauses of query + input-set - value from query :in" + [entid-fn db schema where var-name input-set] + (set (for [var-value input-set] + (if (lookup-ref? schema where var-name var-value) + (entid-fn db var-value) + var-value)))) ;;;;;;;; q function that gives pattern, datoms, and results all in one ;;;;;;;; query. db should be first of args (for now. later, finding @@ -457,7 +501,7 @@ vars (vec (get-all-vars eavs)) newqm (merge qm {:find vars :where where}) ;; This doesn't seem to be getting used anymore - ;newq (qm-to-query newqm) + ;;newq (qm-to-query newqm) dbvarmap (make-dbarg-map (:in qm) args) fixed-args (->> (zipmap (:in qm) args) (map (fn [[sym arg]] @@ -465,9 +509,9 @@ r (apply (partial (:q dcfg) newqm) fixed-args) lookup-ref-patterns (->> args - ;; Would be nice to check by the schema as well, to make sure this is actually a identity attribute - (filter (every-pred vector? (comp keyword? first) (comp (partial = 2) count))) - (map (fn [[a v]] ['$ '_ a v])))] + ;; Would be nice to check by the schema as well, to make sure this is actually a identity attribute + (filter (every-pred vector? (comp keyword? first) (comp (partial = 2) count))) + (map (fn [[a v]] ['$ '_ a v])))] (merge (when (some #{:datoms :datoms-t} retrieve) (let [datoms (split-datoms (create-q-datoms r eavs vars))] @@ -491,7 +535,7 @@ {:results ((:q dcfg) {:find (vec (:find qm)) :in [[vars '...]]} - (vec r))}) + (vec r))}) (when (some #{:patterns :filter-patterns :simple-patterns} retrieve) (let [in-vars (get-input-sets (:q dcfg) (:in qm) args) @@ -499,11 +543,16 @@ (vec (cons db (map - #(if-let [v (in-vars %)] - (resolve-any-idents (:entid dcfg) - (:db (get dbvarmap db)) - v) - %) eav)))) + (fn [var-name] + (if-let [var-value (in-vars var-name)] + (resolve-any-idents (:entid dcfg) + (:db (get dbvarmap db)) + (:schema (get dbvarmap db)) + where + var-name + var-value) + var-name)) + eav)))) (concat lookup-ref-patterns eavs)) qvar-count (count-qvars eavs-ins) linked-qvars (set (remove nil? (map (fn [[k v]] (if (> v 1) k)) qvar-count))) diff --git a/src/posh/lib/update.cljc b/src/posh/lib/update.cljc index 21195df..2b802ff 100644 --- a/src/posh/lib/update.cljc +++ b/src/posh/lib/update.cljc @@ -34,6 +34,20 @@ :reload-fn posh.lib.update/update-filter-pull}) :patterns :ref-patterns)))) +(defn update-pull-many [{:keys [dcfg retrieve] :as posh-tree} storage-key] + ;;(println "updated pull-many: " storage-key) + (let [[_ poshdb pull-pattern eids] storage-key] + (let [analysis (pa/pull-many-analyze dcfg + (cons :patterns retrieve) + (db/poshdb->analyze-db posh-tree poshdb) + pull-pattern + eids)] + (dissoc + (merge analysis + {:reload-patterns (:patterns analysis) + :reload-fn posh.lib.update/update-pull-many}) + :patterns)))) + (declare update-q) (defn update-q-with-dbvarmap [{:keys [dcfg retrieve] :as posh-tree} storage-key] @@ -86,4 +100,3 @@ :pull (update-pull posh-tree storage-key) :q (:analysis (update-q posh-tree storage-key)) :filter-pull (update-filter-pull posh-tree storage-key))) - diff --git a/src/posh/plugin_base.cljc b/src/posh/plugin_base.cljc index 97d4f24..da85f29 100644 --- a/src/posh/plugin_base.cljc +++ b/src/posh/plugin_base.cljc @@ -92,31 +92,31 @@ (if-let [r (get-in @posh-atom [:reactions storage-key])] r (-> - (swap! - posh-atom - (fn [posh-atom-val] - (let [posh-atom-with-query (add-query-fn posh-atom-val) - query-result (:results (get (:cache posh-atom-with-query) storage-key)) - query-ratom (or (get (:ratoms posh-atom-with-query) storage-key) - ((:ratom dcfg) query-result)) - query-reaction ((:make-reaction dcfg) - (fn [] - ;;(println "RENDERING: " storage-key) - @query-ratom) - :on-dispose - (fn [_ _] - ;;(println "no DISPOSING: " storage-key) - (when-not (= (:cache options) :forever) - (swap! posh-atom - (fn [posh-atom-val] - (assoc (p/remove-item posh-atom-val storage-key) - :ratoms (dissoc (:ratoms posh-atom-val) storage-key) - :reactions (dissoc (:reactions posh-atom-val) storage-key)))))))] - (assoc posh-atom-with-query - :ratoms (assoc (:ratoms posh-atom-with-query) storage-key query-ratom) - :reactions (assoc (:reactions posh-atom-with-query) storage-key query-reaction))))) - :reactions - (get storage-key)))) + (swap! + posh-atom + (fn [posh-atom-val] + (let [posh-atom-with-query (add-query-fn posh-atom-val) + query-result (:results (get (:cache posh-atom-with-query) storage-key)) + query-ratom (or (get (:ratoms posh-atom-with-query) storage-key) + ((:ratom dcfg) query-result)) + query-reaction ((:make-reaction dcfg) + (fn [] + ;;(println "RENDERING: " storage-key) + @query-ratom) + :on-dispose + (fn [_ _] + ;;(println "no DISPOSING: " storage-key) + (when-not (= (:cache options) :forever) + (swap! posh-atom + (fn [posh-atom-val] + (assoc (p/remove-item posh-atom-val storage-key) + :ratoms (dissoc (:ratoms posh-atom-val) storage-key) + :reactions (dissoc (:reactions posh-atom-val) storage-key)))))))] + (assoc posh-atom-with-query + :ratoms (assoc (:ratoms posh-atom-with-query) storage-key query-ratom) + :reactions (assoc (:reactions posh-atom-with-query) storage-key query-reaction))))) + :reactions + (get storage-key)))) ([dcfg posh-atom storage-key add-query-fn] (make-query-reaction dcfg posh-atom storage-key add-query-fn {}))) @@ -143,6 +143,19 @@ (u/update-pull @posh-atom storage-key) :reload-fn))) +(defn pull-many + ([dcfg poshdb pull-pattern eids options] + (let [true-poshdb (get-db dcfg poshdb) + storage-key [:pull-many true-poshdb pull-pattern eids] + posh-atom (get-posh-atom dcfg poshdb)] + (make-query-reaction dcfg + posh-atom + storage-key + #(p/add-pull-many % true-poshdb pull-pattern eids) + options))) + ([dcfg poshdb pull-pattern eids] + (pull-many dcfg poshdb pull-pattern eids {}))) + (defn pull-tx [dcfg tx-patterns poshdb pull-pattern eid] (println "pull-tx is deprecated. Calling pull without your tx-patterns.") (pull dcfg poshdb pull-pattern eid)) @@ -242,6 +255,7 @@ (def ~'pull (partial posh.plugin-base/pull ~dcfg)) (def ~'pull-info (partial posh.plugin-base/pull-info ~dcfg)) (def ~'pull-tx (partial posh.plugin-base/pull-tx ~dcfg)) + (def ~'pull-many (partial posh.plugin-base/pull-many ~dcfg)) (def ~'parse-q-query (partial posh.plugin-base/parse-q-query ~dcfg)) (def ~'q-args-count (partial posh.plugin-base/q-args-count ~dcfg)) (def ~'q (partial posh.plugin-base/q ~dcfg)) diff --git a/src/posh/reagent.cljs b/src/posh/reagent.cljs index 1e9cf53..f76a176 100644 --- a/src/posh/reagent.cljs +++ b/src/posh/reagent.cljs @@ -1,7 +1,7 @@ (ns posh.reagent (:require-macros [reagent.ratom :refer [reaction]]) (:require [posh.plugin-base :as base - :include-macros] + :include-macros true] [datascript.core :as d] [reagent.core :as r] [reagent.ratom :as ra])) @@ -9,6 +9,7 @@ (def dcfg (let [dcfg {:db d/db :pull* d/pull + :pull-many d/pull-many :q d/q :filter d/filter :with d/with @@ -18,6 +19,6 @@ :conn? d/conn? :ratom r/atom :make-reaction ra/make-reaction}] - (assoc dcfg :pull (partial base/safe-pull dcfg)))) + (assoc dcfg :pull (partial base/safe-pull dcfg)))) (base/add-plugin dcfg) diff --git a/test/posh/lib/datascript_test.cljc b/test/posh/lib/datascript_test.cljc new file mode 100644 index 0000000..3da3251 --- /dev/null +++ b/test/posh/lib/datascript_test.cljc @@ -0,0 +1,143 @@ +(ns posh.lib.datascript-test + (:require [clojure.test :refer [is deftest testing]] + [datascript.core :as dt] + [posh.clj.datascript :as d])) + +(deftest test-simple-query + (let [conn (dt/create-conn {:a {:db/unique :db.unique/identity}}) + _ (d/posh! conn) + tran-a (d/transact! conn [{:a "foo"}]) + eid (->> (d/q '[:find ?e + :where [?e :a "foo"]] conn) + deref + ffirst)] + (is (some? eid) "Entity should be returned from basic matching query"))) + +;; NOTE: Hardcoding in a lookup-ref in :where isn't supposed to work -- so only testing :in here +;; https://docs.datomic.com/on-prem/identity.html#lookup-refs +(deftest test-lookup-ref-in-eid + (testing "Lookups refs work within query :in as entity-id" + (let [conn (dt/create-conn {:a {:db/unique :db.unique/identity}}) + _ (d/posh! conn) + tran-a (d/transact! conn [{:a "foo" + :b "bar"}]) + b (->> (d/q '[:find ?b + :in $ ?lookup + :where + [?lookup :b ?b]] + conn [:a "foo"]) + deref + first)] + (is (= b ["bar"]))))) + +(deftest test-lookup-ref-in-value + (testing "Lookups refs work within query value as reference value" + (let [conn (dt/create-conn {:a {:db/unique :db.unique/identity} + :b {:db/valueType :db.type/ref}}) + _ (d/posh! conn) + tran-a (d/transact! conn [{:a "foo" + :b {:a "foo2"}}]) + b (->> (d/q '[:find ?aval + :in $ ?lookup + :where + [?e :b ?lookup] + [?e :a ?aval]] + conn [:a "foo2"]) + deref + first)] + (is (= b ["foo"]))))) + +(deftest test-lookup-ref-transact + (testing "Lookups refs work via db/transact" + (let [conn (dt/create-conn {:a {:db/unique :db.unique/identity}}) + _ (d/posh! conn) + tran-a (d/transact! conn [{:a "foo" + :b "bar"}]) + ent-a (->> (d/q '[:find ?e + :where [?e :a "foo"]] conn) + deref + ffirst + (dt/entity (dt/db conn)) + dt/touch) + tran-b (d/transact! conn [[:db/add [:a "foo"] :b "zim"]]) + ent-b (->> (d/q '[:find ?e + :where [?e :a "foo"]] conn) + deref + ffirst + (dt/entity (dt/db conn)) + dt/touch)] + (is (= (:b ent-a) "bar")) + (is (= (:b ent-b) "zim") "lookup ref overwrote previous value") + (is (= (:db/id ent-a) (:db/id ent-b)) "lookup ref modified same entity as original tx")))) + +(deftest test-tuple-value-in + (testing "Query for tuple values works via :in" + (let [conn (dt/create-conn) + _ (d/posh! conn) + tran (d/transact! conn [{:a ["foo" "bar"] + :b 42}]) + ent (->> (d/q '[:find ?e + :in $ ?v + :where [?e :a ?v]] conn ["foo" "bar"]) + deref + ffirst + (dt/entity (dt/db conn)) + dt/touch)] + (= ["foo" "bar"] (:a ent)) + (= 42 (:b ent))))) + +(deftest test-tuple-value-where + (testing "Query for tuple values works in :where clause" + (let [conn (dt/create-conn) + _ (d/posh! conn) + tran (d/transact! conn [{:a ["foo" "bar"] + :b 42}]) + ent (->> (d/q '[:find ?e + :where [?e :a ["foo" "bar"]]] conn) + deref + ffirst + (dt/entity (dt/db conn)) + dt/touch)] + (= ["foo" "bar"] (:a ent)) + (= 42 (:b ent))))) + +(deftest test-basic-pull-reaction + (testing "Basic pull returns entity reaction which updates on entity's transact" + (let [conn (dt/create-conn) + _ (d/posh! conn) + tran (d/transact! conn [{:a "foo" :b 42}]) + eid (->> (d/q '[:find ?e + :where [?e :a "foo"]] conn) + deref + ffirst) + entity-reaction (d/pull conn '[*] eid)] + (is (= (select-keys @entity-reaction [:a :b]) + {:a "foo" :b 42}) "entity-reaction derefs to first transacted value") + (d/transact! conn [{:a "baz" :b 42}]) + (is (= (select-keys @entity-reaction [:a :b]) + {:a "foo" :b 42}) "entity-reaction contains correct value after unrelated tx") + (d/transact! conn [(assoc @entity-reaction :a "bar" :b 43)]) + (is (= (select-keys @entity-reaction [:a :b]) + {:a "bar" :b 43}) "entity-reaction derefs to later transacted value")))) + +(deftest test-pull-many + (testing "pull-many returns entity reaction which updates on any entity's transact" + (let [conn (dt/create-conn) + _ (d/posh! conn) + ents [{:a "foo" :b 42} + {:a "bar" :b 52} + {:a "baz" :b 62}] + tran (d/transact! conn ents) + eids (->> (d/q '[:find ?e + :where [?e :a _]] conn) + deref + (reduce into []) + reverse) + ;;dtlg-raw (dt/pull-many (dt/db conn) '[*] eids) + entity-reaction (d/pull-many conn '[*] eids)] + (is (= ents (map #(select-keys % [:a :b]) @entity-reaction)) + "Entities in reaction should match input entities against input sequence of eids") + (let [updated-ents (vec (map #(update % :b inc) @entity-reaction))] + (d/transact! conn updated-ents) + (is (= updated-ents @entity-reaction) + "Entities in reaction should updated after transact")))))