-
-
Notifications
You must be signed in to change notification settings - Fork 311
Description
This is a backwards compatible change that would vastly improve DX in my opinion
I've always wondered: why are entities not transactable? I find myself converting entities to maps all the time solely to transact them. This still causes problems when entities nest other entities. So here are a few simple ideas on how entities could be treated in transactions:
1. Entities could be treated as refs in transactions
(def schema
{:user/friends #:db{:valueType :db.type/ref
:cardinality :db.cardinality/many}})
(def ent (d/touch (d/entity @conn 1)))
ent ; eval
;; =>
{:db/id 1
:user/email "[email protected]"
:user/friends #{{:db/id 2} {:db/id 3}}} ; <-- nested entities
Now I convert it to a map
(def ent-map (into {:db/id (:db/id ent)} ent))
ent-map ; eval
;; =>
{:db/id 1
:user/email "[email protected]"
:user/friends #{{:db/id 2} {:db/id 3}}}
;; looks the same but nested entities (under :user/friends) have not been converted
I try to transact it
(d/transact! conn [(assoc ent-map :user/email "[email protected]")])
;; throws:
;; Execution error (ExceptionInfo) at datascript.db/entid (db.cljc:385).
;; Expected number or lookup ref for entity id, got #:db{:id 2}
So I can either dissoc the :user/friends
map-entry or convert contained entities to refs
(d/transact! conn [(-> ent-map
(dissoc :genstyle.project/population)
;; OR (update :user/friends #(mapv :db/id %))
(assoc :user/email "[email protected]"))])
We could spare ourselves from this by treating entities as refs in transactions. The database already walks nested data-structures to resolve refs so why not resolve entities as refs, also?
2. Entities to return maps on update
datascript.impl.entity/Entity
implements clojure.lang.Associative
which currently only throws errors:
clojure.lang.Associative
;; some methods elided
(empty [e] (throw (UnsupportedOperationException.)))
(assoc [e k v] (throw (UnsupportedOperationException.)))
(cons [e [k v]] (throw (UnsupportedOperationException.)))
Instead assoc
could return a hashmap
(deftype Entity [db eid touched cache]
;; elided
clojure.lang.Associative
(assoc [e k v]
(let [e-map (cond-> {:db/id eid}
; add other kvals if touched
touched (into e))]
(assoc e-map k v))))
This would also make update
possible. Together this means that the change of email to ent
from above, could look like this:
(d/transact! conn [(assoc ent :user/email "[email protected]")])
I would've already implemented this for my own projects but unfortunately Clojure (unlike ClojureScript) doesn't allow to overwrite a Type's methods. To achieve this one would have to for DataScript and change the code of datascript.impl.entity/Entity
so I wanted to raise the issue here first and see what @tonsky's thoughts are.
This would also unlock a more straightforward use of libraries like meander or specter to walk and update entities.