register-pure-sub: uses a function to do the work

This commit is contained in:
Stuart Mitchell 2016-06-09 18:43:55 +12:00
parent a5f73a3991
commit 48a1bda438
3 changed files with 282 additions and 3 deletions

View File

@ -29,7 +29,7 @@
[karma-junit-reporter "0.3.8"]]}
:cljsbuild {:builds [{:id "test"
:source-paths ["test"]
:source-paths ["test" "src"]
:compiler {:output-to "run/compiled/test.js"
:source-map "run/compiled/test.js.map"
:output-dir "run/compiled/test"

102
src/re_frame/subs-2.cljs Normal file
View File

@ -0,0 +1,102 @@
(ns re-frame.subs2
(:require
[cljs.spec :as s]
[re-frame.subs :as subs]
[reagent.ratom :as ratom
:include-macros true]))
(s/def ::args (s/cat
:sub-name keyword?
:sub-fn (s/? fn?)
:arrow-args (s/* (s/cat :key #{:<-} :val (s/* vector?)))
:f fn?))
(defn- fmap
"Returns a new version of 'm' in which f has been applied to each value.
(fmap inc {:a 4, :b 2}) => {:a 5, :b 3}"
[f m]
(into {} (for [[k val] m] [k (f val)])))
(defn- make-multi-map
"takes a squence of keys and values and makes a multimap
also removes the spec extra-values"
[s]
(-> s
(->> (group-by first)
(fmap #(->> %
(map second)
(map :val))))))
(defn- multi-deref
"derefs a map sequence or a singleton"
[data]
(cond
(map? data) (fmap deref data)
(coll? data) (map deref data)
:else @data))
(s/fdef register-pure
:args ::args)
(defn register-pure
"This fn allows the user to write a 'pure' subscription
i.e. that is a subscription that operates on the values within app-db
rather than the atom itself
Note there are 3 ways this function can be called
```(register-pure
:test-sub
(fn [db [_]] db))```
In this example the entire app-db is derefed and passed to the subscription
function as a singleton
```(subs/register-pure
:a-b-sub
(fn [q-vec d-vec]
[(subs/subscribe [:a-sub])
(subs/subscribe [:b-sub])]
(fn [[a b] [_]] {:a a :b b}))```
In this example the the first function is called with the query vector
and the dynamic vector as arguements the return value of this function
can be singleton reaction or a list or map of reactions. Note that `q-vec`
and `d-vec` can be destructured and used in the subscriptions (this is the point
actually). Again the subscriptions are derefed and passed to the subscription
function
```(subs/register-pure
:a-b-sub
:<- [:a-sub]
:<- [:b-sub]
(fn [[a b] [_]] {:a a :b b}))```
In this example the convienent syntax of `:<-` is used to cover the majority
of cases where only a simple subscription is needed without any parameters
"
[& args]
(let [conform (s/conform ::args args)
{:keys [sub-name
sub-fn
arrow-args
f]} conform
arrow-subs (->> arrow-args
(mapcat :val))]
(cond
sub-fn ;; first case the user provides a custom sub-fn
(subs/register
sub-name
(fn [db q-vec d-vec]
(let [subscriptions (sub-fn q-vec d-vec)] ;; this let needs to be outside the fn
(ratom/make-reaction
(fn [] (f (multi-deref subscriptions) q-vec d-vec))))))
arrow-args ;; the user uses the :<- sugar
(subs/register
sub-name
(fn [db q-vec d-vec]
(let [subscriptions (map subs/subscribe arrow-subs)] ;; this let needs to be outside the fn
(ratom/make-reaction
(fn [] (f (multi-deref subscriptions) q-vec d-vec))))))
:else
(subs/register ;; the simple case with no subs
sub-name
(fn [db q-vec d-vec]
(ratom/make-reaction (fn [] (f @db q-vec d-vec))))))))

View File

@ -2,13 +2,14 @@
(:require [cljs.test :refer-macros [is deftest]]
[reagent.ratom :refer-macros [reaction]]
[re-frame.subs :as subs]
[re-frame.subs2 :as subs2]
[re-frame.db :as db]
[re-frame.core :as re-frame]))
;=====test basic subscriptions ======
(deftest test-reg-sub
(subs/clear-handlers!)
(subs/register
:test-sub
@ -20,6 +21,7 @@
(is (= 1 @test-sub))))
(deftest test-chained-subs
(subs/clear-handlers!)
(subs/register
:a-sub
@ -40,4 +42,179 @@
(reset! db/app-db {:a 1 :b 2})
(is (= {:a 1 :b 2} @test-sub))
(swap! db/app-db assoc :b 3)
(is (= {:a 1 :b 3} @test-sub))))
(is (= {:a 1 :b 3} @test-sub))))
(deftest test-sub-parameters
(subs/clear-handlers!)
(subs/register
:test-sub
(fn [db [_ b]] (reaction [(:a @db) b])))
(let [test-sub (subs/subscribe [:test-sub :c])]
(reset! db/app-db {:a 1 :b 2})
(is (= [1 :c] @test-sub))))
(deftest test-sub-chained-parameters
(subs/clear-handlers!)
(subs/register
:a-sub
(fn [db [_ a]] (reaction [(:a @db) a])))
(subs/register
:b-sub
(fn [db [_ b]] (reaction [(:b @db) b])))
(subs/register
:a-b-sub
(fn [db [_ c]]
(let [a (subs/subscribe [:a-sub c])
b (subs/subscribe [:b-sub c])]
(reaction {:a @a :b @b}))))
(let [test-sub (subs/subscribe [:a-b-sub :c])]
(reset! db/app-db {:a 1 :b 2})
(is (= {:a [1 :c], :b [2 :c]} @test-sub))))
;============== test register-pure macros ================
(deftest test-reg-sub-macro
(subs/clear-handlers!)
(subs2/register-pure
:test-sub
(fn [db [_]] db))
(let [test-sub (subs/subscribe [:test-sub])]
(is (= @db/app-db @test-sub))
(reset! db/app-db 1)
(is (= 1 @test-sub))))
(deftest test-reg-sub-macro-singleton
(subs/clear-handlers!)
(subs2/register-pure
:a-sub
(fn [db [_]] (:a db)))
(subs2/register-pure
:a-b-sub
(fn [_ _ _]
(subs/subscribe [:a-sub]))
(fn [a [_]]
{:a a}))
(let [test-sub (subs/subscribe [:a-b-sub])]
(reset! db/app-db {:a 1 :b 2})
(is (= {:a 1} @test-sub))
(swap! db/app-db assoc :b 3)
(is (= {:a 1} @test-sub))))
(deftest test-reg-sub-macro-vector
(subs/clear-handlers!)
(subs2/register-pure
:a-sub
(fn [db [_]] (:a db)))
(subs2/register-pure
:b-sub
(fn [db [_]] (:b db)))
(subs2/register-pure
:a-b-sub
(fn [_ _ _]
[(subs/subscribe [:a-sub])
(subs/subscribe [:b-sub])])
(fn [[a b] [_]]
{:a a :b b}))
(let [test-sub (subs/subscribe [:a-b-sub])]
(reset! db/app-db {:a 1 :b 2})
(is (= {:a 1 :b 2} @test-sub))
(swap! db/app-db assoc :b 3)
(is (= {:a 1 :b 3} @test-sub))))
(deftest test-reg-sub-macro-map
(subs/clear-handlers!)
(subs2/register-pure
:a-sub
(fn [db [_]] (:a db)))
(subs2/register-pure
:b-sub
(fn [db [_]] (:b db)))
(subs2/register-pure
:a-b-sub
(fn [_ _ _]
{:a (subs/subscribe [:a-sub])
:b (subs/subscribe [:b-sub])})
(fn [{:keys [a b]} [_]]
{:a a :b b}))
(let [test-sub (subs/subscribe [:a-b-sub])]
(reset! db/app-db {:a 1 :b 2})
(is (= {:a 1 :b 2} @test-sub))
(swap! db/app-db assoc :b 3)
(is (= {:a 1 :b 3} @test-sub))))
(deftest test-sub-macro-parameters
(subs/clear-handlers!)
(subs2/register-pure
:test-sub
(fn [db [_ b]] [(:a db) b]))
(let [test-sub (subs/subscribe [:test-sub :c])]
(reset! db/app-db {:a 1 :b 2})
(is (= [1 :c] @test-sub))))
(deftest test-sub-macros-chained-parameters
(subs/clear-handlers!)
(subs2/register-pure
:a-sub
(fn [db [_ a]] [(:a db) a]))
(subs2/register-pure
:b-sub
(fn [db [_ b]] [(:b db) b]))
(subs2/register-pure
:a-b-sub
(fn [[_ c] _]
[(subs/subscribe [:a-sub c])
(subs/subscribe [:b-sub c])])
(fn [[a b] [_ c]] {:a a :b b}))
(let [test-sub (subs/subscribe [:a-b-sub :c])]
(reset! db/app-db {:a 1 :b 2})
(is (= {:a [1 :c] :b [2 :c]} @test-sub))))
(deftest test-sub-macros-chained-parameters-<-
"test the syntactial sugar"
(subs/clear-handlers!)
(subs2/register-pure
:a-sub
(fn [db [_]] (:a db)))
(subs2/register-pure
:b-sub
(fn [db [_]] (:b db)))
(subs2/register-pure
:a-b-sub
:<- [:a-sub]
:<- [:b-sub]
(fn [[a b] [_ c]] {:a a :b b}))
(let [test-sub (subs/subscribe [:a-b-sub :c])]
(reset! db/app-db {:a 1 :b 2})
(is (= {:a 1 :b 2} @test-sub) ))
)