introduce fx/defn macro and fx/merge function

intended to deprecate `handlers-macro/merge-fx`

Signed-off-by: yenda <eric@status.im>
This commit is contained in:
yenda 2018-09-24 15:28:57 +02:00
parent bf2e51f856
commit ed2abf9101
No known key found for this signature in database
GPG Key ID: 0095623C0069DCE6
5 changed files with 164 additions and 2 deletions

View File

@ -0,0 +1,42 @@
(ns status-im.utils.fx
(:refer-clojure :exclude [defn]))
(defmacro defn
"Defines an fx producing function
Takes the same arguments as the defn macro
Produces a 2 arity function:
- first arity takes the declared parameters and returns a function that takes cofx as
single argument, for use in composition of effects
- second arity takes cofx as first arguments and declared parameters as next arguments,
for use in repl or direct call
Notes:
- destructuring of cofx is possible
- supports docstring
- supports attr-map
- TODO: add suport for `prepost-map?` (don't forget to add it to arglist)
- TODO: add validation of macro parameters"
{:arglists '([name doc-string? attr-map? [params*] body])}
[name & fdecl]
(let [m (if (string? (first fdecl))
{:doc (first fdecl)}
{})
fdecl (if (string? (first fdecl))
(next fdecl)
fdecl)
m (if (map? (first fdecl))
(conj m (first fdecl))
m)
fdecl (if (map? (first fdecl))
(next fdecl)
fdecl)
[cofx & args] (first fdecl)
fdecl (next fdecl)
argsyms (take (count args) (repeatedly #(gensym "arg")))]
`(clojure.core/defn ~(with-meta name m)
([~@argsyms] (fn [cofx#] (~name cofx# ~@argsyms)))
([cofx# ~@args]
(if (and (map? cofx#)
(not (nil? (:db cofx#))))
(let [~cofx cofx#]
~@fdecl)
(throw (js/Error. (str "fx/defn expects a map of cofx as first argument got " cofx# " in function " ~name))))))))

View File

@ -0,0 +1,51 @@
(ns status-im.utils.fx
(:require-macros status-im.utils.fx
[taoensso.timbre :as log])
(:require [clojure.set :as set])
(:refer-clojure :exclude [merge]))
(defn- update-db [cofx fx]
(if-let [db (:db fx)]
(assoc cofx :db db)
cofx))
(def ^:private mergable-keys
#{:data-store/tx :data-store/base-tx :chat-received-message/add-fx
:shh/add-new-sym-keys :shh/get-new-sym-keys :shh/post
:shh/generate-sym-key-from-password :confirm-messages-processed
:utils/dispatch-later})
(defn- safe-merge [fx new-fx]
(if (:merging-fx-with-common-keys fx)
fx
(let [common-keys (set/intersection (into #{} (keys fx))
(into #{} (keys new-fx)))]
(if (empty? (set/difference common-keys (conj mergable-keys :db)))
(clojure.core/merge (apply dissoc fx mergable-keys)
(apply dissoc new-fx mergable-keys)
(merge-with into
(select-keys fx mergable-keys)
(select-keys new-fx mergable-keys)))
(do (log/error "Merging fx with common-keys: " common-keys)
{:merging-fx-with-common-keys common-keys})))))
(defn merge
"Takes a map of co-effects and forms as argument.
The first optional form can be map of effects
The next forms are functions applying effects and returning a map of effects.
The fn ensures that updates to db are passed from function to function within the cofx :db key and
that only a :merging-fx-with-common-keys effect is returned if some functions are trying
to produce the same effects (excepted :db, :data-source/tx and :data-source/base-tx effects).
:data-source/tx and :data-source/base-tx effects are handled specially and their results
(list of transactions) are compacted to one transactions list (for each effect). "
[{:keys [db] :as cofx} & args]
(let [[first-arg & rest-args] args
initial-fxs? (map? first-arg)
fx-fns (if initial-fxs? rest-args args)]
(reduce (fn [fxs fx-fn]
(let [updated-cofx (update-db cofx fxs)]
(if fx-fn
(safe-merge fxs (fx-fn updated-cofx))
fxs)))
(if initial-fxs? first-arg {:db db})
fx-fns)))

View File

@ -27,7 +27,8 @@
to produce the same effects (excepted :db, :data-source/tx and :data-source/base-tx effects).
:data-source/tx and :data-source/base-tx effects are handled specially and their results
(list of transactions) are compacted to one transactions list (for each effect). "
{:added "1.0"}
{:added "1.0"
:deprecated "Please use utils.fx/merge function instead"}
[cofx & forms]
(let [form (first forms)]
(if (or (symbol? form)

View File

@ -51,7 +51,7 @@
[status-im.test.utils.http]
[status-im.test.init.core]
[status-im.test.ui.screens.add-new.models]
[status-im.test.utils.fx]
[status-im.test.accounts.recover.core]
[status-im.test.hardwallet.core]
[status-im.test.ui.screens.currency-settings.models]
@ -114,6 +114,7 @@
'status-im.test.utils.keychain.core
'status-im.test.utils.universal-links.core
'status-im.test.utils.http
'status-im.test.utils.fx
'status-im.test.ui.screens.add-new.models
'status-im.test.accounts.recover.core
'status-im.test.hardwallet.core

View File

@ -0,0 +1,67 @@
(ns status-im.test.utils.fx
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.utils.fx :as fx]))
(fx/defn hello
"this is a very nice useless function"
[{:keys [db]} a]
{:db (assoc db :a a)})
(fx/defn hello2
{:doc "this function is useless as well"}
[{:keys [db]} b]
{:db (assoc db :a b) :b (:a db)})
(fx/defn hello3
{:doc "lol lazy function does nothing"}
[{:keys [db]} b]
(identity nil))
(deftest merge-fxs-test
(testing "merge function for fxs"
(let [cofx {:db {:c 2}}]
(is (= (fx/merge cofx
(hello "a")
(hello2 "b"))
{:db {:c 2
:a "b"}
:b "a"}))
(testing "with initial fxs map"
(is (= (fx/merge cofx
{:potatoe :potating}
(hello "a")
(hello2 "b"))
{:db {:c 2
:a "b"}
:b "a"
:potatoe :potating})
"initial fxs map should be merged in the result"))
(testing "with a nil producing function"
(is (= (fx/merge cofx
(hello "a")
(hello3 "c")
(hello2 "b"))
{:db {:c 2
:a "b"}
:b "a"})))
(testing "with condition statement"
(testing "false"
(is (= (let [do-hello? false]
(fx/merge cofx
(when do-hello?
(hello "a"))
(hello2 "b")))
{:db {:c 2
:a "b"}
:b nil})
"the conditional statement should not apply"))
(testing "true"
(is (= (let [do-hello? true]
(fx/merge cofx
(when do-hello?
(hello "a"))
(hello2 "b")))
{:db {:c 2
:a "b"}
:b "a"})
"the conditional statement should apply"))))))