Welcome! Please see the About page for a little more info on how this works.

+6 votes
in Client API by

The recommendation for enums in Datomic is to use :db/ident. Using :db/ident as an enum value complicates the use with d/pull. Everywhere you pull an entity, you'll need to write code to deal with the nested enum entity. For example, say I have a :user/type enum that is defined as suggested by that doc (card one, ref attr). Anytime I pull my :user/type attribute, I need to write code to unnest the :user/type value.

(d/pull (d/db conn) '[:user/type] [:user/id "a"])
=> #:user{:type #:db{:id 87960930222153, :ident :user.type/a}}

How are folks dealing with this? Just always wrapping the d/pull return with something like (update :user/type :db/ident)? Perhaps always remembering to specify the pull pattern for all enum attributes as (:user/type :xform my-ns/get-db-ident), where my-ns/get-db-ident is just (def get-db-ident :db/ident)?

I asked this question in the #datomic channel in Clojurians Slack and figured I'd move it here so it would persist. Other folks replied with some other ideas.

Tyler Nisonoff suggested a postwalk from every pull result. e.g.,

    #(match %
       {:db/ident ident} ident
       :else %)

2 Answers

+1 vote

We use :xform + custom metadata on 'enum' attributes to fixup our pulls automagically

+1 vote

This is the helper I'm using, I'm sure it performs better than the one you referred. Also, it doesn't require you to explicitly pull :db/ident fields and it works on pull-many and pull results. Consider replacing map with mapv if you want to retain vectors instead of lazy seqs.

(defn resolve-idents
  "Helper that resolves all idents in a pull expression return to
  their :db/ident keyword."
  [db x]
  (cond (map? x)
        (if-let [ident (d/ident db (:db/id x))]
          (reduce-kv (fn [x k v]
                       (assoc x k (resolve-idents db v)))
                     x x))
        (vector? x)
        (map #(resolve-idents db %) x)

Regarding the question how to deal with this:

Use a helper like this and wrap it in your own version of pull/pull-many:

(defn full 
  "Version of pull that resolves idents"
  [db expr id]
  (->> (d/pull db expr id) (resolve-idents db)))

Regarding this topic in general:

I'd really be interested in Cognitects reasons to implement this behavior differently from the good old entity API. There are obviously some pros and cons, but it would be interesting to know the decisive reasons.