Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,25 @@ As with `create`, we can call the `:user/upsert` database function directly:
:email "[email protected]"}])
```

If we want to update an entity with a known ID, we can specify in the function
```clojure
(models.user/upsert conn entity-id {:name "Sally's new name"
:email "[email protected]"})
```

or in the transactor function:

```clojure
(d/transact conn [:user/upsert entity-id {:name "Sally's new name"
:email "[email protected]"}])
```

**Note:** If you specify an ID it will be assumed that this is the element you want to update (even if it has a different or unspecified unique-identity). Make sure you're actually updating the right thing!

When `upsert` is updating, it will be smart about many-valued attributes. Updating an attribute with the value `[1 2 3]` to have the value `[2 3 4]` will add `4` **and** retract `1`. Normal datomic doesn't do this!



# Querying data

Crustacean generates three functions for querying data in each namespace `defmodel` is called in:
Expand Down
2 changes: 1 addition & 1 deletion project.clj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
(defproject appcanary/crustacean "0.2.0-SNAPSHOT"
(defproject appcanary/crustacean "0.2.1-SNAPSHOT"
:description "CRU operations & migrations for datomic"
:url "http://example.com/FIXME"
:license {:name "Apache License Version 2.0"
Expand Down
2 changes: 1 addition & 1 deletion src/crustacean/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@
(let [upsert-fn (keyword (:namespace entity) "upsert")]
(fn [conn dirty-input]
(let [input (remove-nils dirty-input)]
(s/validate (:input-schema entity) input)
#_(s/validate (:input-schema entity) input)
(let [tempid (d/tempid :db.part/user -1)
{:keys [tempids db-after]} @(d/transact conn [[upsert-fn tempid input]])
id (d/resolve-tempid db-after tempids tempid)]
Expand Down
91 changes: 60 additions & 31 deletions src/crustacean/db_funcs.clj
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,19 @@
(defn create-fn
"The `create` database function for a given entity"
[entity]
(d/function
`{:lang :clojure
:params [~'db ~'id ~'input]
:code
(if-let [malformed# (d/invoke ~'db (keyword ~(:namespace entity) "malformed?") ~'input)]
(throw (IllegalArgumentException. (str malformed#)))

(if (d/invoke ~'db (keyword ~(:namespace entity) "exists?") ~'db ~'input)
(throw (IllegalStateException. "entity already exists"))
(d/function
`{:lang :clojure
:params [~'db ~'id ~'input]
:code
(if-let [malformed# (d/invoke ~'db (keyword ~(:namespace entity) "malformed?") ~'input)]
(throw (IllegalArgumentException. (str malformed#)))
(if (d/invoke ~'db (keyword ~(:namespace entity) "exists?") ~'db ~'input)
(throw (IllegalStateException. "entity already exists"))

(vector
~(generate-tx-map entity)
[:db/add (d/tempid :db.part/tx) ~(keyword (:namespace entity) "txCreated") ~'id] ;;annotate the transactionx
)))}))
(vector
~(generate-tx-map entity)
[:db/add (d/tempid :db.part/tx) ~(keyword (:namespace entity) "txCreated") ~'id] ;;annotate the transactionx
)))}))

(defn upsert-fn
"The `upsert` database function for the model"
Expand All @@ -57,19 +56,48 @@
`{:lang :clojure
:params [~'db ~'id ~'input]
:code
(if-let [malformed# (d/invoke ~'db (keyword ~(:namespace model) "malformed?") ~'input)]
(throw (IllegalArgumentException. (str malformed#)))

(if (d/invoke ~'db (keyword ~(:namespace model) "exists?") ~'db ~'input)
(vector
~(generate-tx-map model false) ; Exclude defaults cuz it's an update
[:db/add (d/tempid :db.part/tx) ~(keyword (:namespace model) "txUpdated") ~'id] ;;annotate the transaction
)
(vector
~(generate-tx-map model)
[:db/add (d/tempid :db.part/tx) ~(keyword (:namespace model) "txCreated") ~'id] ;;annotate the transaction
)
))}))
(if-let [malformed# false #_(d/invoke ~'db (keyword ~(:namespace model) "malformed?") ~'input)]
;; Temporarily getting rid of this malformed? check because we may want to update entities in situations without specifying required keys (or specifying keys that aren't permitted)
;; I need to rewrite how malformed is handled for upserts/updates
(throw (IllegalArgumentException. (str malformed#)))

(if-let [entity-id# (if (= Long (class ~'id))
; If the id is a long it's not a tempid so it's an update
; tempids are datomic.db.DbId
~'id
;; Maybe the entity exists
(d/invoke ~'db (keyword ~(:namespace model) "exists?") ~'db ~'input))]
(let [entity# (d/entity ~'db entity-id#)
txes# (->> (for [[k# v#] ~'input]
(let [namespaced-key# (keyword ~(:namespace model) (name k#))
[type# opts#] (get ~(:fields model) (name k#))
current# (cond->> (get entity# namespaced-key#)

(= :ref type#)
(map :db/id))]
(cond (and (nil? v#) current#)
[[:db/retract ~'id namespaced-key# v#]]

(contains? opts# :many)
(let [[to-add# to-retract#] (clojure.data/diff (set v#) current#)]
(concat
(for [x# to-add#]
[:db/add ~'id namespaced-key# x#])
(for [x# to-retract#]
[:db/retract ~'id namespaced-key# x#])))

(not= v# current#)
(do (println v# current#)
[[:db/add ~'id namespaced-key# v#]]))))
(apply concat)
(remove nil?))]
(when (not-empty txes#)
(conj txes#
[:db/add (d/tempid :db.part/tx) ~(keyword (:namespace model) "txUpdated") ~'id])))
(vector
~(generate-tx-map model)
[:db/add (d/tempid :db.part/tx) ~(keyword (:namespace model) "txCreated") ~'id] ;;annotate the transaction
)))}))

(defn exists-fn
"The `exists?` database function for a given entity"
Expand All @@ -86,11 +114,12 @@
(map (fn [[a# b#]]
[(keyword ~(:namespace entity) (name a#)) (get input# a#)
(keyword ~(:namespace entity) (name b#)) (get input# b#)])))]
(seq (concat
(d/q {:find '[[~'?e ...]] :in '[~'$ [[~'?attr ~'?value]]] :where '[[~'?e ~'?attr ~'?value]]} db# unique-key-pairs#)
(mapcat (fn [[attr1# value1# attr2# value2#]]
(d/q {:find '[[~'?e ...]] :in '[~'$ ~'?value1 ~'?value2] :where `[[~'~'?e ~attr1# ~'~'?value1] [~'~'?e ~attr2# ~'~'?value2]]} db# value1# value2#))
composite-key-pairs#))))}))
(or
(d/q {:find '[~'?e .] :in '[~'$ [[~'?attr ~'?value]]] :where '[[~'?e ~'?attr ~'?value]]} db# unique-key-pairs#)
(some identity
(mapcat (fn [[attr1# value1# attr2# value2#]]
(d/q {:find '[~'?e .] :in '[~'$ ~'?value1 ~'?value2] :where `[[~'~'?e ~attr1# ~'~'?value1] [~'~'?e ~attr2# ~'~'?value2]]} db# value1# value2#))
composite-key-pairs#))))}))

(defn malformed-fn
"The malformed? database function for a given entity"
Expand Down
1 change: 1 addition & 0 deletions src/crustacean/utils.clj
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
~'exists?
~'pull
~'create
~'upsert
~'pull-many
~'all-with
~'find-by
Expand Down