thank you @nathanmarz, this combo looks solid!
here is my final version to upsert into the re-frame database:
(defn upsert [db path key-eq new]
(letfn [(map-eq? [old] (= (get new key-eq) (get old key-eq)))
(updater [old] [(merge old new)])]
(let [full-path (conj path s/ALL map-eq?)
[db2 updated?] (s/replace-in full-path updater db)]
(if updated?
db2
(s/setval (conj path s/AFTER-ELEM) new db)))))
@igrishaev FWIW, I had a problem very similar to yours, and for it I implemented a simple custom data structure that keeps values in a vector and a map simultaneously. It implements all IPersistentMap methods, and assoc
works as the upsert.
@igrishaev fyi, use the path
macro for composing paths instead of conj
will be much faster
thank you, will check that out
in my case, path
is a vector to the actual data subset, say [:tasks :completed]
yea, you should pass that in as (path :tasks :completed)
path
uses inline caching to minimize runtime compilation of paths
If I have a vector like [:foo :bar]
, how can I apply it the the path
macro then?
(path :foo :bar)
sure, but what if both foo and bar come as a vector?
as a func argument in my case
if it's dynamic, then declare them using path
, not as a vector
(upsert db (path :foo :bar) ...)
the problem is I don’t know in advance what subset of the main db will be used.
I supposed a user passes an initial path as a vector, then I build the full transformation path
ultimately it's an optimization
if your users declare their paths using path
it will be faster, but if they use vectors it will still work
ok, so if a user passes smth like (path :foo :bar)
as an initial path, how can I extend it?
with path
e.g. (path passed-in-user-path ALL)
oh, I see now, thanks