# 06: spec-gen

If you're not familiar with clojure.spec, check out [the spec guide on clojure.org](https://clojure.org/guides/spec). It's very well-written.

Our code:

```scheme
(ns reifyhealth.specmonstah-tutorial.06
  (:require [reifyhealth.specmonstah.core :as sm]
            [clojure.spec.alpha :as s]
            [clojure.spec.gen.alpha :as gen]
            [reifyhealth.specmonstah.spec-gen :as sg]))

(s/def ::id (s/and pos-int? #(< % 100)))
(s/def ::not-empty-string (s/and string? not-empty #(< (count %) 10)))

(s/def ::username ::not-empty-string)
(s/def ::user (s/keys :req-un [::id ::username]))

(s/def ::name ::not-empty-string)
(s/def ::topic (s/keys :req-un [::id ::name ::owner-id]))

(s/def ::owner-id ::id)
(s/def ::topic-id ::id)
(s/def ::content ::not-empty-string)
(s/def ::post (s/keys :req-un [::id ::owner-id ::topic-id ::content]))

(def schema
  {:user  {:prefix :u
           :spec   ::user}
   :topic {:prefix    :t
           :spec      ::topic
           :relations {:owner-id [:user :id]}}
   :post  {:prefix    :p
           :spec      ::todo
           :relations {:topic-id [:topic :id]}}})

(defn ex-01
  []
  {:user  (gen/generate (s/gen ::user))
   :topic (gen/generate (s/gen ::topic))
   :post  (gen/generate (s/gen ::post))})
```

First we define some specs (lines 7-19) to generate a little dummy data. Here's what the raw generated data looks like:

```scheme
(ex-01) ;=>
{:user  {:id 2,  :username "G95seixU"},
 :topic {:id 11, :name "stA9xO50w", :owner-id 1},
 :post  {:id 57, :owner-id 2, :topic-id 2, :content "937x"}}
```

That's useful, but we can't insert that into a test database because the foreign keys wouldn't match the values they reference. The `:topic`'s `:owner-id`, for example, is `1`, where the `:user`'s `:id` is `2`.

We can use `reifyhealth.specmonstah.spec-gen/ent-db-spec-gen` to generate data and then assign the foreign keys:

```scheme
(defn ex-02
  []
  (:data (sg/ent-db-spec-gen {:schema schema} {:post [[1]]})))

(ex-02)
; =>
{:nodeset #{:t0 :topic :p0 :u0 :post :user},
 :adj     {:post #{:p0}, :p0 #{:t0}, :topic #{:t0}, :t0 #{:u0}, :user #{:u0}},
 :in      {:p0 #{:post}, :t0 #{:topic :p0}, :u0 #{:t0 :user}},
 :attrs   {:post  {:type :ent-type},
           :p0    {:type                 :ent,
                   :index                0,
                   :ent-type             :post,
                   :query-term           [1],
                   :loom.attr/edge-attrs {:t0 {:relation-attrs #{:topic-id}}},
                   :spec-gen             {:id 2, :owner-id 7, :topic-id 16, :content "3IU"}},
           :topic {:type :ent-type},
           :t0    {:type                 :ent,
                   :index                0,
                   :ent-type             :topic,
                   :query-term           [:_],
                   :loom.attr/edge-attrs {:u0 {:relation-attrs #{:owner-id}}},
                   :spec-gen             {:id 16, :name "FM4fcV3t", :owner-id 2}},
           :user  {:type :ent-type},
           :u0    {:type       :ent,
                   :index      0,
                   :ent-type   :user,
                   :query-term [:_],
                   :spec-gen   {:id 2, :username "xh"}}}}
```

Oh wow, OK. That's a lot to look at. Let's step through it.

We're looking at the value for the ent db's `:data` key. This is the loom graph that we've looked at in earlier sections, the graph returned by `add-ents` that captures ents and their relationships. Under the `:attrs` key, you can see that each ent (`:p0`, `:t0`, and `:u0`) now has the attribute `:spec-gen`. Under `:spec-gen` is a map that's been generated using clojure.spec, except that the foreign keys have been updated to be correct.

Sometimes you want to view just the data that clojure.spec has generated; viewing the entire ent db is overwhelming. To make that easier, Specmonstah has the `reifyhealth.specmonstah.core/attr-map` function:

```
(defn ex-03
  []
  (-> (sg/ent-db-spec-gen {:schema schema} {:todo [[1]]})
      (sm/attr-map :spec-gen)))

(ex-03)
;; =>
{:p0 {:id 30, :owner-id 6, :topic-id 11, :content "03hK"}
 :t0 {:id 11, :name "A4rq01NK", :owner-id 84}
 :u0 {:id 84, :username "QN8J68"}}
```

`attr-map` returns a map where the keys are ent names and the values are the value of the given node attribute (`:spec-gen` here) for each ent. There's a convenience function that combines `sg/ent-db-spec-gen` and `sm/attr-map`, `sg/ent-db-spec-gen-attr`:

```
(defn ex-04
  []
  (sg/ent-db-spec-gen-attr {:schema schema} {:post [[1]]}))

(ex-04)
;; =>
{:p0 {:id 2, :owner-id 20, :topic-id 2, :content "573AAM1D"}
 :t0 {:id 2, :name "6q7a4", :owner-id 2}
 :u0 {:id 2, :username "h"}}
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://sweet-tooth.gitbook.io/specmonstah/tutorial/06-spec-gen.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
