Let’s say I have a schema that includes an attribute :x/value
, where :x/value
is a component, is a ref, and has cardinality many. The schema also has an id for x :x/id
.
Now let’s say I say I transact the following:
(d/transact conn [{:x/id "1234" :x/value [{:text "test"}]}])
Then later I want to update the value, meaning really that I want to replace :x/value
, so that in the end I have an entity like this:
{:db/id <some eid>
:x/id "1234"
:x/value [{:text "replacement"}]}
How would I do this?
So far, I've tried the following:
(d/transact conn [{:x/id "1234" :x/value [{:text "replacement"}]}])
But this simply added a new ref, so I got an entity looking like:
{:db/id <some eid>
:x/id "1234"
:x/value [{:text "test"} {:text "replacement"}]}
One way to achieve what I want here, I think, would be to manually retract both of the :text attributes by their entity id, and then do a new add transaction for the x
entity.
But I wonder if there's a better way to do this. Any ideas?
You need to retract the old value and then update it with a new value:
[:db/retract entity-id attribute old-value]
[:db/add entity-id attribute new-value]
See http://docs.datomic.com/transactions.html
You can see more details in the James Bond example from Tupelo Datomic. Here is how the attributes are created:
(td/transact *conn* ; required required zero-or-more
; <attr name> <attr value type> <optional specs ...>
(td/new-attribute :person/name :db.type/string :db.unique/value) ; each name is unique
(td/new-attribute :person/secret-id :db.type/long :db.unique/value) ; each secret-id is unique
(td/new-attribute :weapon/type :db.type/ref :db.cardinality/many) ; one may have many weapons
(td/new-attribute :location :db.type/string) ; all default values
(td/new-attribute :favorite-weapon :db.type/keyword )) ; all default values
Suppose James throws his knife at a villan. We need to remove it from the DB.
(td/transact *conn*
(td/retract-value james-eid :weapon/type :weapon/knife))
(is (= (td/entity-map (live-db) james-eid) ; lookup by EID
{:person/name "James Bond" :location "London" :weapon/type #{:weapon/wit :weapon/gun} :person/secret-id 7 } ))
Once James has defeated Dr No, we need to remove him (& everything he possesses) from the database.
; We see that Dr No is in the DB...
(let [tuple-set (td/find :let [$ (live-db)]
:find [?name ?loc] ; <- shape of output tuples
:where {:person/name ?name :location ?loc} ) ]
(is (= tuple-set #{ ["James Bond" "London"]
["M" "London"]
["Dr No" "Caribbean"]
["Honey Rider" "Caribbean"] } )))
; we do the retraction...
(td/transact *conn*
(td/retract-entity [:person/name "Dr No"] ))
; ...and now he's gone!
(let [tuple-set (td/find :let [$ (live-db)]
:find [?name ?loc]
:where {:person/name ?name :location ?loc} ) ]
(is (= tuple-set #{ ["James Bond" "London"]
["M" "London"]
["Honey Rider" "Caribbean"] } )))
Using native datomic is almost identical, just not quite as sweet as Tupelo:
; Dr No is no match for James. He gives up trying to use guile...
; Remove it using native Datomic.
(spy :before (td/entity-map (live-db) [:person/name "Dr No"]))
(d/transact *conn*
[[:db/retract [:person/name "Dr No"] :weapon/type :weapon/guile]])
(is (= (spy :after (td/entity-map (live-db) [:person/name "Dr No"])) ; LookupRef
{:person/name "Dr No"
:location "Caribbean"
:weapon/type #{:weapon/knife :weapon/gun}}))
:before => {:person/name "Dr No",
:weapon/type #{:weapon/guile :weapon/knife :weapon/gun},
:location "Caribbean"}
:after => {:person/name "Dr No",
:weapon/type #{:weapon/knife :weapon/gun},
:location "Caribbean"}
Side Note: I noticed that your example shows :arb/value [{:db/id 17592186045435, :content/text "tester"}]
, which is a list of maps of length 1. This is different than my example where :weapon/type is just a plain set of N items. This output difference is because you are using the pull
API of Datomic. However, this won't affect your original problem.
We all know that James has had many Bond girls over the years. Here is an example of how to add in some honeys and them demote one of them:
(defn get-bond-girl-names []
(let [result-pull (d/pull (live-db) [:bond-girl] [:person/name "James Bond"])
bond-girl-names (forv [girl-entity (grab :bond-girl result-pull) ]
(grab :person/name (td/entity-map (live-db) (grab :db/id girl-entity))))
]
bond-girl-names))
(td/transact *conn*
(td/new-attribute :bond-girl :db.type/ref :db.cardinality/many)) ; there are many Bond girls
(let [tx-result @(td/transact *conn*
(td/new-entity {:person/name "Sylvia Trench"})
(td/new-entity {:person/name "Tatiana Romanova"})
(td/new-entity {:person/name "Pussy Galore"})
(td/new-entity {:person/name "Bibi Dahl"})
(td/new-entity {:person/name "Octopussy"})
(td/new-entity {:person/name "Paris Carver"})
(td/new-entity {:person/name "Christmas Jones"}))
tx-datoms (td/tx-datoms (live-db) tx-result)
girl-datoms (vec (remove #(= :db/txInstant (grab :a %)) tx-datoms))
girl-eids (mapv :e girl-datoms)
txr-2 (td/transact *conn*
(td/update [:person/name "James Bond"] ; update using a LookupRef
{:bond-girl girl-eids})
(td/update [:person/name "James Bond"] ; don't forget to add Honey Rider!
{:bond-girl #{[:person/name "Honey Rider"]}}))
]
(is (= (get-bond-girl-names)
["Sylvia Trench" "Tatiana Romanova" "Pussy Galore" "Bibi Dahl"
"Octopussy" "Paris Carver" "Christmas Jones" "Honey Rider"]))
; Suppose Bibi Dahl is just not refined enough for James. Give her a demotion.
(td/transact *conn*
(td/retract-value [:person/name "James Bond"] :bond-girl [:person/name "Bibi Dahl"]))
(newline)
(is (= (get-bond-girl-names) ; Note that Bibi Dahl is no longer listed
["Sylvia Trench" "Tatiana Romanova" "Pussy Galore"
"Octopussy" "Paris Carver" "Christmas Jones" "Honey Rider"] ))
)
Note that you can only use a LookupRef
like [:person/name "Honey Rider"]
since the attribute :person/name
has :db.unique/value
. If your :content/text
is not :db.unique/value
you'll have to use an EID to detach it from the parent entity.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With