I have a situation where I have an entity A with a cardinality:many ref to an entity B.
A is already transacted to the database. I'm looking for the most efficient way to add entity B and add it to the ref attribute of A.
For example, already-transacted entity A might look like this:
(def john
{:teacher/first "John"
:teacher/last "Doe"
:teacher/email "johndoe@university.edu"})
And yet-to-be entity B might look like this:
(def brandon {:student/id (d/squuid)
:student/first "Brandon"
:student/last "Smith"})
I hoped I could just issue a transaction with B tacked on to A via the ref field, but that didn't seem to work:
(d/transact conn [(conj john {:user/students brandon})])
;; ==> ":db.error/not-an-entity Unable to resolve entity: :user/students",
;; ==> ":db.error/not-an-entity Unable to resolve entity: :user/students",
The only way I've gotten this to work is an unwieldily sequence where I transact entity B, query back its ID, and then link them up:
(let [brandon-tx (d/transact conn [brandon])
new-db (:db-after @brandon-tx)
brandon-id (-> (d/q '[:find (pull ?e [*])
:in $ ?bid
:where [?e :student/id ?bid]] new-db (:student/id brandon)) ffirst :db/id)
tx [:db/add john-id :teacher/students brandon-id]
result (d/transact conn [tx])
]
result)
Is there a better way to transact entity B and reference it in A? Full code below:
(require '[datomic.api :as d])
(def uri "datomic:mem://db")
(d/create-database uri)
(def conn (d/connect uri))
(def db (d/db conn))
(def schema
;; teachers
[{:db/ident :teacher/first
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one}
{:db/ident :teacher/last
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one}
{:db/ident :teacher/email
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/unique :db.unique/identity}
{:db/ident :teacher/students
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/many}
;; students
{:db/ident :student/first
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one}
{:db/ident :student/last
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one}
{:db/ident :student/id
:db/valueType :db.type/uuid
:db/cardinality :db.cardinality/one
:db/unique :db.unique/identity}
])
(d/transact conn schema)
;; teachers
(def john
{:teacher/first "John"
:teacher/last "Doe"
:teacher/email "johndoe@university.edu"})
(def jane
{:teacher/first "Jane"
:teacher/last "Doe"
:teacher/email "janedoe@university.edu"})
(d/transact conn [john jane])
;; #<promise$settable_future$reify__8061@7dd5e085:
;; {:db-before datomic.db.Db@723be74,
;; :db-after datomic.db.Db@35baf542,
;; :tx-data
;; [#datom[13194139534316 50 #inst "2023-08-05T17:37:44.466-00:00" 13194139534316 true]],
;; :tempids
;; {-9223301668109598125 17592186045418,
;; -9223301668109598124 17592186045419}}>
@(def all-teachers (d/q '[:find (pull ?e [*])
:where [?e :teacher/email]] (d/db conn)))
;; ==> [[{:db/id 17592186045418,
;; :teacher/first "John",
;; :teacher/last "Doe",
;; :teacher/email "johndoe@university.edu"}]
;; :db/id 17592186045419,
;; :teacher/first "Jane",
;; :teacher/last "Doe",
;; :teacher/email "janedoe@university.edu"}]]
;; students
(def ashley {:student/id (d/squuid)
:student/first "Ashley"
:student/last "Johnson"})
(def brandon {:student/id (d/squuid)
:student/first "Brandon"
:student/last "Smith"})
;; add students to a teacher
@(def john-id (ffirst (d/q '[:find ?e
:where
[?e :teacher/email "johndoe@university.edu"]] (d/db conn))))
@(def add-brandon-to-john (conj john {:user/students brandon}))
;; ==> {:teacher/first "John",
;; :teacher/last "Doe",
;; :teacher/email "johndoe@university.edu",
;; :user/students #:student{:id #uuid"7e098355-3ce8-4a7d-bd47-d7ea4e0819a4",
;; :first "Brandon",
;; :last "Smith"}}
;; fails
(d/transact conn [(conj john {:user/students brandon})])
;; ==> ":db.error/not-an-entity Unable to resolve entity: :user/students",
;; succeeds
(let [brandon-tx (d/transact conn [brandon])
new-db (:db-after @brandon-tx)
brandon-id (-> (d/q '[:find (pull ?e [*])
:in $ ?bid
:where [?e :student/id ?bid]] new-db (:student/id brandon)) ffirst :db/id)
tx [:db/add john-id :teacher/students brandon-id]
result (d/transact conn [tx])
]
result)
It turns out I didn't update some of my code after I refactored. I can add entity B and reference it in one step. user
should have been teacher
.
(d/transact conn [(conj john {:teacher/students brandon})])