Fork me on GitHub
#biff
<
2022-12-24
>
2FO22:12:35

Howdy, I'm running into this error when I attempt to write to a db field that's modeled as an`:enum` in Malli. Partial stack trace

[qtp1095227075-25] ERROR com.biffweb.impl.middleware - Exception while handling request
clojure.lang.ExceptionInfo: Doc wouldn't be a valid :game after transaction. {:tx-doc {:db/doc-type :game, :xt/id #uuid "1ce03e5a-6f0f-460f-bdf6-52f2d2ac6572", :game/name "Tetris", :game/difficulty "hard"}, :explain {:game/difficulty ["invalid type"]}}...
Form
(def game-form [_]
...
[:select
      {:name "difficulty"}
      [:option {:value :easy} "easy"]
      [:option {:value :medium} "medium"]
      [:option {:value :hard} "hard"]]
      ...
Form Handler
(defn new-game [{:keys [params] :as req}]
  (biff/submit-tx req
                  [{:db/doc-type :game
                    :xt/id (random-uuid)
                    :game/name (:name params)
                    :game/difficulty (:difficulty params)}])})
Schema
...
   :game/id :uuid
   :game [:map {:closed true}
          [:xt/id :game/id]
          [:game/name :string]
          [:game/difficulty [:set  [:enum :easy :medium :hard]]]]
          ...

Jacob O'Bryant22:12:31

I think the value of difficulty being passed to submit-tx is a string, when your schema says it should be a set of keywords. you can throw in a print call to make sure, though I think it'll work if you pass the value to keyword and remove the :set bit from your schema

1
2FO00:12:31

🙏, yep :set was the problem, it passes without it, both as a keyword and a string . Have I got the wrong idea about :set ? I assumed it prevented duplication in the db .eg .."Tetris", :game/difficulty :hard :hard but perhaps it's only meant to prevent duplication at the transaction level. At the document level, how might you handle a game update where there could be the potential for duplicated difficulty levels? Or similarly a favorite list where multiple game id's could appear in a vector but no more than once?

Jacob O'Bryant02:12:55

Can a single game have multiple difficulties? If so I probably gave you the wrong advice. (I was assuming a :game document means a specific instance of a game, e.g. "alice and bob are now playing yahtzee", vs. the more general concept of "yahtzee" itself--I'll blame it on skimming your message from my phone earlier 🙂) If a game can have multiple difficulties, than your schema was right to begin with, and you can instead update the submit-tx call to wrap the value in a set:

(defn new-game [{:keys [params] :as req}]
  (biff/submit-tx req
                  [{:db/doc-type :game
                    :xt/id (random-uuid)
                    :game/name (:name params)
                    :game/difficulty (set (keyword (:difficulty params)))}])})

Jacob O'Bryant02:12:17

If you're updating an existing document and you want to insert another value into a set, you can also use :db/union -- see https://biffweb.com/docs/reference/transactions/ for an example

Jacob O'Bryant02:12:49

Out of curiosity--if a game can have multiple difficulties, is game-form intended to provide multiple select? It's currently just a single-select, right?

ikitommi12:12:25

not sure if this helps, but the latest main from malli can coerce homogenous :enum values from strings to keywords, symbols and numbers: https://github.com/metosin/malli/pull/782

🙏 1
2FO22:12:46

You had it right the first time, each game should have a single difficulty 😄. Apologies, my follow-up question isn't easy to follow. :game/difficulty should compare the difficulties between the concept of a game not an instance:

.. {:game/name "chess", :game/difficulty :hard}
.. {:game/name "monopoly", :game/difficulty :easy}
Given that, how might I prevent the following update function from passing in multiple difficulties? E.g. {.. "chess", :game/difficulty :hard :easy} Update handler
...
  (biff/submit-tx req
    [{:db/op :update
      :db/doc-type :game
      :xt/id (:parse uuid)
      :game/difficulty (:difficulty params)}])
  ...)
Game-update form
[:select
      {:name "difficulty"}
      [:option {:value :easy} "easy"]
      [:option {:value :medium} "medium"]
      [:option {:value :hard} "hard"]]

Jacob O'Bryant23:12:11

re: coercion--that's handy, I wasn't aware of that!

Jacob O'Bryant00:12:32

that update handler will overwrite the difficulty value, so there will only be a single value. the document won't have multiple values unless you explicitly pass in a vector/set/some other type of collection

1
Jacob O'Bryant00:12:55

I wonder if you're thinking about this in an entity-attribute-value centric way, similar to how datomic works? this is an example of how XT's document-centric approach differs--when you submit a document it completely overwrites the entire previous document; it doesn't add individual facts accumulatively. (feel free to ignore if this just confuses things more!)

🙏 1
2FO18:12:59

Thinking in terms of small updates rather than overwrites was exactly my confusion. I'll give the biff and XTDB transaction docs a good read later. Thanks

🙌 1