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:
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 theent'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 isadded as an ent attr using the key
:visit-key
:query-opts
- any query opts like{:refs {} :spec-gen {}}
forthe 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 entAttribute polymorphism (e.g. if a
like
can refer to eitherpost
or atopic
)
Last updated
Was this helpful?