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
)))