# 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](https://sweet-tooth.gitbook.io/specmonstah/tutorial/05-progressive-construction), 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`)
