This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-07-15
Channels
- # beginners (16)
- # cljs-dev (12)
- # clojure (9)
- # clojure-berlin (5)
- # clojure-russia (16)
- # clojure-uk (26)
- # clojurescript (48)
- # community-development (2)
- # cursive (1)
- # data-science (10)
- # datomic (7)
- # emacs (27)
- # figwheel-main (31)
- # fulcro (6)
- # hoplon (47)
- # immutant (1)
- # jobs (1)
- # jobs-discuss (33)
- # off-topic (3)
- # onyx (34)
- # protorepl (5)
- # re-frame (26)
- # reagent (1)
- # reitit (1)
- # shadow-cljs (80)
- # spacemacs (44)
- # specter (4)
- # testing (1)
- # tools-deps (4)
Hi! In specter, is there a possibility to perform an upsert operation? For example, I’ve got a vector of maps like this:
[{:id 1 :name "John"}
{:id 2 :name "Jim"}
{:id 3 :name "Jane"}]
Now a new map arrives and, depending on its id I need either to update (merge) it with an existing map or append it to the end of the vector. I now how to implement both insert/update separately, but a single transform/setval
api call would be much more preferable.As I see it, the only thing I don’t understand is how to compose a transformation path.
@igrishaev you can do it with a single path with zippers;
(require '[com.rpl.specter.zipper :as z])
(def truefn (constantly true))
(def target
(recursive-path [id] p
(cond-path
[z/NODE #(= (:id %) id)]
[z/NODE (submap nil)]
(not-selected? z/NEXT)
[z/INNER-RIGHT AFTER-ELEM]
truefn
[z/NEXT p]
)))
(defn upsert [data n]
(setval [z/VECTOR-ZIP z/DOWN (target (:id n))] n data))
(upsert data {:id 1 :name "Joze"})
;; => [{:id 1, :name "Joze"} {:id 2, :name "Jim"} {:id 3, :name "Jane"}]
(upsert data {:id 0 :name "Joze"})
;; => [{:id 1, :name "John"} {:id 2, :name "Jim"} {:id 3, :name "Jane"} {:id 0, :name "Joze"}]
but I would do it like this:
(defn upsert [data n]
(let [[data2 new] (replace-in [ALL #(= (:id %) (:id n)) (submap nil)]
(fn [_] [n nil])
data)]
(if (nil? new)
(setval AFTER-ELEM n data)
data2
)))