Search code examples
clojureclojurescript

Swap nested specific item in map finding by key value, the position in map


We have a cursor or atom map with this example data:

    #<Cursor: [:customer] {:name Diego Peña, 

    :addresses [{:id 23, :province Madrid, :country 1, :descripcion aaeeeeeeee iii oooo4444, :locality Gali gali, :country_name SPAIN, :direccion Street Cierva, :id 3, :postalcode 30203, :principal true, :customer 17} 

{:id 35, :province Madrid, :country nil, :descripcion yyy lalala3, :locality Lalala, :direccion calle Maria 3 , :postalcode 333, :principal false, :customer 17} 

{:id 6, :province Madrid, :country 2, :descripcion otra direccioncita444, :locality Leleele, :country_name SPAIN, :direccion Direccion calle Ooo, :postalcode 1236, :main false, :customer 17} 

{:id 27, :province Madrid, :country 1, :descripcion grandisima, :locality Alcantarilla, :country_name SPAIN, :direccion C/ 3 Mayo, :postalcode 3001, :main false, :customer 17}

]}>

I need to change the values of a searched address by id. I have managed to locate the address by the value of the id:

(defn get-address [pk]
   (->> @db/customer :addresses (filter #(= (int pk) (int (:id %)))) first)
)

I can change all addresses with this: :ok #(swap! db/customer assoc-in [:addresses] %)}). I need to change data for a specific address from the API response.

I am close to getting it, but with this approach I am missing the position or the index of the item: #(swap! db/client assoc-in [:addresses ¿position or index in map?] %) we have the id of item address.

Perhaps this approach is wrong, a better one?


Solution

  • The assoc, assoc-in, update, and update-in functions work also on vectors. In Clojure, vectors are associative data structures where the key is the numeric index (O..n) and the value is the item at position n.

    So you can do:

    (assoc [:a :b :c] 1 :new-value)
    ;;      ^  ^  ^
    ;;      0  1  2
    ;; => [:a :new-value :c]
    

    Based on your example, you will need this:

    (defn address-index-by-id
      "Take an `address` map and look it up by `:id` in the `addresses` list.
       Return the numeric index where it was found, nil if not found."
      [address addresses]
      (->> (map-indexed vector addresses) 
           ;; produce a seq `([0 val-at-index-0] … [n val-at-index-n])`
           (filter (fn [[_index {:keys [id]}]] (= id  (:id address)))) ;; filter by id
           (ffirst) ;; get the index of the first match
           ))
    
    (defn set-address
      "Take a `customer` map and an `address` map. Will put the `address` in the
      customer's addresses list. If an address with the same :id key is already
      present in this list, it will be overwritten."
      [customer address]
      (if-let [existing-index (address-index-by-id address (:addresses customer))]
        (assoc-in customer [:addresses existing-index] address)
        (update customer :addresses conj address)))
    

    Usage:

    (set-address {:name      "Diego Peña"
                  :addresses []}
                 {:id       1
                  :province "Madrid"})
    ;; => {:name "Diego Peña", :addresses [{:id 1, :province "Madrid"}]}
    
    (-> {:name      "Diego Peña"
         :addresses [{:id       1
                      :province "Madrid"
                      :main     true}
                     {:id       2
                      :province "Barcelona"
                      :main     false}]}
        (set-address {:id       2
                      :province "Barcelona"
                      :main     true})
        (set-address {:id       1
                      :province "Madrid"
                      :main     false}))
    ;; => {:name "Diego Peña", :addresses [{:id 1, :province "Madrid", :main false} {:id 2, :province "Barcelona", :main true}]}
    
    ;; And of course if your `customer` is stored in an Atom:
    (swap! customer set-address {:id 1, :province "Madrid", :main true})