Search code examples
clojuredatomic

How do I build a transaction that has references to a variable number of entities?


I'm getting into datomic and still don't grok it. How do I build a transaction that has references to a variable number of entities?

For example this creates a transaction with a child entity and a family entity with a child attribute that references the new child entity:

(defn insert-child [id child]
  {:db/id #db/id id
   :child/first-name (:first-name child)
   :child/middle-name (:middle-name child)
   :child/last-name (:last-name child)
   :child/date-of-birth {:date-of-birth child}})

(defn insert-family [id]
  (let [child-id #db/id[:db.part/user]]
    (vector
     (insert-child child-id
                   {:first-name "Richard"
                    :middle-name "M"
                    :last-name "Stallman"})
     {:db/id id
      :family/child child-id})))

(insert-family #db/id[:db.part/user])
=> [{:db/id #db/id[:db.part/user -1000012], 
     :child/first-name "Richard", 
     :child/middle-name "M", 
     :child/last-name "Stallman", 
     :child/date-of-birth nil} 
    {:db/id #db/id[:db.part/user -1000013], 
     :family/child #db/id[:db.part/user -1000012]}]

Notice I used let for child-id. I'm not sure how to write this such that I can map over insert-child while having a family entity that references each one.

I thought about using iterate over #db/id[:db.part/user] and the number of children then mapping over both the result of iterate and a vector of children. Seems kind of convoluted and #db/id[:db.part/user] isn't a function to iterate over to begin with.


Solution

  • Instead of using the macro form #db/id[:db.part/user] which is meant for EDN files and data literals, you should use d/tempid.

    You could do something like this (using simplified child entities):

    (ns family-tx
        (:require [datomic.api :refer [q db] :as d]))
    
    (def uri "datomic:mem://testfamily")
    (d/delete-database uri)
    (d/create-database uri)
    (def conn (d/connect uri))
    
    (def schema [
                  {:db/id (d/tempid :db.part/db)
                   :db/ident :first-name
                   :db/valueType :db.type/string
                   :db/cardinality :db.cardinality/one
                   :db.install/_attribute :db.part/db}
                  {:db/id (d/tempid :db.part/db)
                   :db/ident :last-name
                   :db/valueType :db.type/string
                   :db/cardinality :db.cardinality/one
                   :db.install/_attribute :db.part/db}
                  {:db/id (d/tempid :db.part/db)
                   :db/ident :family/child
                   :db/valueType :db.type/ref
                   :db/cardinality :db.cardinality/many
                   :db.install/_attribute :db.part/db}
                ])
    @(d/transact conn schema)
    
    
    (defn make-family-tx [kids]
      (let [kids-tx (map #(into {:db/id (d/tempid :db.part/user)} %) kids)
            kids-id (map :db/id kids-tx)]
              (conj kids-tx {:db/id (d/tempid :db.part/user)
                             :family/child kids-id})))
    
    
    (def kids [{:first-name "Billy" :last-name "Bob"}
               {:first-name "Jim" :last-name "Beau"}
               {:first-name "Junior" :last-name "Bacon"}])
    
    @(d/transact conn (make-family-tx kids))
    

    There are a few strategies for this discussed in the Transactions docs as well (see the "Identifying Entities" section).