Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating value with cardinality many

Tags:

datomic

I have a schema like this:

[{:db/id #db/id[:db.part/db]
  :db/ident :person/name
  :db/valueType :db.type/string
  :db/cardinality :db.cardinality/one
  :db/doc "A person's name"
  :db.install/_attribute :db.part/db}
 {:db/id #db/id[:db.part/db]
  :db/ident :person/roles
  :db/valueType :db.type/keyword
  :db/cardinality :db.cardinality/many
  :db/doc "A person's role"
  :db.install/_attribute :db.part/db}]

And a code like this:

;; insert new person
(def new-id (-> (d/transact conn [{:db/id (d/tempid :db.part/user)
                                   :person/name "foo"
                                   :person/roles #{:admin}}])
                (:tempids)
                (vals)
                (first)))

(defn get-roles
  [db eid]
  (d/q '[:find ?roles .
         :in $ ?eid 
         :where [?eid :user/roles ?roles]]))

(get-roles (d/db conn) new-id) ;; => [:admin]

;; update a person 
(d/transact conn [{:db/id new-id 
                   :person/roles #{:client}}])

(get-roles (d/db conn) new-id) ;; => [:admin :client]

It seems the default behaviour on it is, it will just assoc the new value.

How can I get this result, after doing the updating transaction:

(get-roles (d/db conn) new-id) ;; => [:client]
like image 609
tsukihi Avatar asked Sep 18 '25 12:09

tsukihi


2 Answers

if what you want is to "reset" the list of roles to a new value (an 'absolute' operation 'in contrast to the 'relative' operations of just adding or removing roles), you'll have to use a transaction function to perform a diff and retract the values that need be.

Here's a basic generic implementation:

{:db/id (d/tempid :db.part/user),
 :db/ident :my.fns/reset-to-many,
 :db/fn
 (d/function
   {:lang :clojure,
    :requires '[[datomic.api :as d]],
    :params '[db e attr new-vals],
    :code
    '(let [ent (or (d/entity db e)
                 (throw (ex-info "Entity not found" 
                          {:e e :t (d/basis-t db)})))
           entid (:db/id ent)
           old-vals (get ent attr)]
       (into
         [{:db/id (:db/id ent)
           ;; adding the new values
           attr new-vals}]
         ;; retracting the old values
         (comp
           (remove (set new-vals))
           (map (fn [v]
                  [:db/retract entid attr v])))
         old-vals)
       )})}

;; Usage
(d/transact conn [[:my.fns/reset-to-many new-id :person/roles #{:client}]])
like image 194
Valentin Waeselynck Avatar answered Sep 21 '25 08:09

Valentin Waeselynck


Here is a solution once suggested from Robert Stuttaford

(defn many-delta-tx
  "Produces the transaction necessary to have
   `new` be the value for `entity-id` at `attr`
   `new` is expected to be a set."
  [db entity-id attr new]
  (let [current (into #{} (map :v)
                      (d/datoms db :eavt
                                entity-id
                                (d/entid db attr)))]
    (concat (for [id (clojure.set/difference new current)]
              [:db/add entity-id attr id])
            (for [id (clojure.set/difference current new)]
              [:db/retract entity-id attr id]))))

For ease of testing, I would like to slightly modify part of the example of original question.

  • Change of the schema. I add db/unique
[{:db/id #db/id[:db.part/db]
   :db/ident :person/name 
   :db/valueType :db.type/string
   :db/cardinality :db.cardinality/one
   :db/unique     :db.unique/identity
   :db/doc "A person's name"
   :db.install/_attribute :db.part/db}
  {:db/id #db/id[:db.part/db]
   :db/ident :person/roles
   :db/valueType :db.type/keyword
   :db/cardinality :db.cardinality/many
   :db/doc "A person's role"
   :db.install/_attribute :db.part/db}]
  • get-roles
(defn get-roles
  [db eid]
  (d/q '[:find [?roles ...]
         :in $ ?eid
         :where [?eid :person/roles ?roles]] db eid))
  • My testing example
;; (many-delta-tx (d/db conn) [:person/name "foo"] :person/roles #{:sales :client})
;; => ([:db/add [:person/name "foo"] :person/roles :sales] [:db/retract [:person/name "foo"] :person/roles :admin]))

like image 31
Laurence Chen Avatar answered Sep 21 '25 08:09

Laurence Chen