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:
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:
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:
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, 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:
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:
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:
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)