# 09: performing inserts

In this section you'll look at how you might insert the data Specmonstah has generated into a database. We'll be adding the data to an atom, but you can apply the idea to your own database. The code:

```scheme
(ns reifyhealth.specmonstah-tutorial.09
  (: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      ::post
           :relations {:topic-id [:topic :id]}}})

(def database (atom []))

(defn insert
  [db {:keys [ent-type visit-val spec-gen]}]
  (when-not visit-val
    (swap! database conj [ent-type spec-gen])))

(defn ex-01
  []
  (reset! database [])
  (-> (sg/ent-db-spec-gen {:schema schema} {:post [[1]]})
      (sm/visit-ents :insert insert))
  @database)

(ex-01)
;; =>
[[:user {:id 7, :username "j29AFqnr"}]
 [:topic {:id 2, :name "Qqo04X1Zo", :owner-id 7}]
 [:post {:id 70, :owner-id 2, :topic-id 2, :content "al"}]]
```

The specs and schema should be familiar by now. Looking at the `ex-01` function, we see that it calls `sg/ent-db-spec-gen`. As you saw earlier, this creates the ent db and uses clojure.spec to generate a map for each ent, storing the map under the ent's `:spec-gen` attribute. The resulting ent db is passed to `sm/visit-ents` with the visit key `:insert` and visiting function `insert`.

`insert` works by:

* Checking whether this ent has already been inserted
* If not, updating the `database` atom by conjing a vector of the

  ent's type and the value generated by `spec-gen`.

Let's go through `insert` line by line:

```scheme
(defn insert
  [db {:keys [ent-type visit-val spec-gen]}]
  (when-not visit-val
    (swap! database conj [ent-type spec-gen])))
```

The second argument to all visiting functions is a map. We're only using the keys `:ent-type`, `:visit-val`, and `:spec-gen`. The full map includes:

* All of the ent's attributes (like `:spec-gen`)
* `:visit-val`, the value from previous visits if there have been any
* `:visit-key` - when the visiting function returns, its value is

  added as an ent attr using the key `:visit-key`
* `:query-opts` - any query opts like `{:refs {} :spec-gen {}}` for

  the ent
* `:visit-query-opts` - any query opts meant for this visiting fn

`(when-not visit-val ...)`, checks whether `insert` has already visited this ent. (I'll explain why you want to perform this check soon.) If the ent hasn't been visited, the `database` gets updated by conjing a vector of the `ent-type` and `spec-gen`. The `database` atom ends up with a value like this:

```scheme
[[:user {:id 7, :username "j29AFqnr"}]
 [:topic {:id 2, :name "Qqo04X1Zo", :owner-id 7}]
 [:post {:id 70, :owner-id 7, :topic-id 2, :content "al"}]]
```

Each ent is inserted in dependency order: `:user` first, then `:topic`, then `:post`.

Now let's revisit `(when-not visit-val ...)`. You want to perform this check because of Specmonstah's progressive construction feature. As we covered in [05: Progressive construction](/specmonstah/tutorial/05-progressive-construction.md), it's possible to pass an ent-db to successive calls to `sm/add-ents`. If you added more ents and wanted to insert, you wouldn't want to re-insert previous ents. `ex-02` demonstrates this:

```scheme
(defn ex-02
  []
  (reset! database [])
  (-> (sg/ent-db-spec-gen {:schema schema} {:post [[1]]})
      (sm/visit-ents :insert insert)
      (sg/ent-db-spec-gen {:post [[3]]})
      (sm/visit-ents :insert insert))
  @database)

(ex-02)
;; =>
[[:user {:id 6, :username "kUQzVd4S"}]
 [:topic {:id 98, :name "I3", :owner-id 6}]
 [:post {:id 9, :owner-id 6, :topic-id 98, :content "CEZ"}]
 [:post {:id 7, :owner-id 6, :topic-id 98, :content "mU391M"}]
 [:post {:id 2, :owner-id 6, :topic-id 98, :content "6ngN"}]
 [:post {:id 28, :owner-id 6, :topic-id 98, :content "m4"}]]
```

The `:user`, `:todo-list`, and `:todo` ents from the first call to `ent-db-spec-gen` are only inserted once, even though they are visited by `insert` multiple times.

In fact, recall that `ent-db-spec-gen` internally calls `sm/add-ents` and then calls the `sg/spec-gen` visiting function. `sg/spec-gen` is written with this same principle in mind: it can visit the ent db multiple times, and won't overwrite any existing values. The pattern is common enough that Specmonstah provides the `sm/visit-ents-once` which you can use instead of `sm/visit-ents`:

```scheme
(defn insert-once
  [db {:keys [ent-type spec-gen]}]
  (swap! database conj [ent-type spec-gen])
  true)

(defn ex-03
  []
  (reset! database [])
  (-> (sg/ent-db-spec-gen {:schema schema} {:post [[1]]})
      (sm/visit-ents-once :insert insert-once)
      (sg/ent-db-spec-gen {:post [[3]]})
      (sm/visit-ents-once :insert insert-once))
  @database)

(ex-03)
;; =>
[[:user {:id 2, :username "MuGc6"}]
 [:topic {:id 87, :name "0Oj9P", :owner-id 2}]
 [:post {:id 2, :owner-id 2, :topic-id 87, :content "c6k3Z1HwI"}]
 [:post {:id 1, :owner-id 2, :topic-id 87, :content "qEjSKQ"}]
 [:post {:id 9, :owner-id 2, :topic-id 87, :content "646nn4bI"}]
 [:post {:id 45, :owner-id 2, :topic-id 87, :content "J2vL0Mgi"}]]
```

Instead of having to write a `when-not` conditional yourself, `visit-ents-once` checks whether *any* value exists for the visiting function (even `nil` or `false`), and if it does, it doesn't apply the visiting function to that ent.

With this coverage of `visit-ents` and `visit-ents-once`, you should be able to handle most use cases. The rest of the guide covers more specific modeling uses cases, like:&#x20;

* Satisfying a database constraint that there be no two records with the same two foreign keys (e.g. if you're building a forum and don't want a user to `like` the same post twice)
* Modeling an attribute that's a vector of foreign keys
* Handling cycles
* Using `binding` to specify that all the refs in an entire hierarchy should refer to a specified ent
* Attribute polymorphism (e.g. if a `like` can refer to either `post` or a `topic`)


---

# 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/09-an-insertion-visiting-function.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.
