04: refs
In all the examples so far, all the todo lists have referred to the same user, :u0. What if you want to create two todo lists, but you want them to belong to different users? Here's how you could do that:
(ns reifyhealth.specmonstah-tutorial.04
(:require [reifyhealth.specmonstah.core :as sm]))
(def schema
{:user {:prefix :u}
:topic {:prefix :t
:relations {:owner-id [:user :id]}}
:post {:prefix :p
:relations {:topic-id [:topic :id]
:owner-id [:user :id]}}})
(defn ex-01
(sm/add-ents {:schema schema} {:topic [[2 {:refs {:owner-id :my-own-sweet-user}}]
(sm/view (ex-01))
What's new in this example is the map {:refs {:owner-id :my-own-sweet-user}}. It resulted in two todo lists, :tl0 and :tl1, referring to a :user ent named :my-own-sweet-user instead of :u0. :t2 refers to :u0, as we've seen before. Let's break this down.
In the schema, :topic includes this relations definition:
:relations {:owner-id [:user :id]}
This means, :topics refer to a user via the :owner-id attribute. Remember that queries are essentially telling Specmonstah, generate the minimal ent-db necessary for me to retrieve the ents I've specified, so when you call the add-ents function and instruct SM to generate a :topic, SM's default behavior is to satisfy this schema definition by creating a :user and naming it according to its default naming system.
Internally, the ent db tracks that the :topic refers to the :user via the :owner-id attribute, and when your query includes the option {:refs {:owner-id :my-own-sweet-user}}, you're saying, I want the user that :owner-id refers to to be named :my-own-sweet-user. One reason you might do this would be to write a test ensuring that users can't modify each others' topics.
If you look back at the schema for this section, you'll notice it introduced a new ent type, :topic, and :posts reference :topics. What if you wanted to create two topics, each with one post and each topic belonging to a different user? Here's how you could do that:
(defn ex-02
(sm/add-ents {:schema schema} {:topic [[1]
[1 {:refs {:owner-id :hamburglar}}]]
:post [[1]
[1 {:refs {:topic-id :t1}}]]}))
(sm/view (ex-02))
Before reading the explanation of how this works, indulge the educator in me and take a moment to write or say your own explanation. Quizzing yourself like this is an effective way to clarify and retain your understanding. There's all kinds of studies that show it; it's called "the testing effect" and it's one of the best ways to learn.
Let's break this query down term by term:
{:topic [[1]
[1 {:refs {:owner-id :hamburglar}}]]
:post [[1]
[1 {:refs {:topic-id :t1}}]]}
Under :topic, [1] tells Specmonstah to create a :topic. It's given the default name :t0. Since you didn't specify any refs, it refers to the default user, :u0.
The next query term, [1 {:refs {:owner-id :hamburglar}}], instructs SM to create a :topic that refers to a user named, of all things, :hamburglar. This :topic is given the default name of :t1.
Under :post, [1] tells Specmonstah to create a :post with a default name and default refs. Therefore, :p0 refers to :t0.
The next term, [1 {:refs {:topic-id :t1}}], tells SM that the next :todo should refer to the :topic named :t1. We specified the :t1 here because we know that Specmonstah's naming system will produce that name for the second :topic specified in the query.
The point of all this is that you can rely on Specmonstah's naming system to reliably and concisely establish the properties and relations of the ent db you want to generate. If you don't want to keep track of Specmonstah's implicit names, you can name things explicitly:
(defn ex-03
(sm/add-ents {:schema schema} {:topic [[:t0]
[:t1 {:refs {:owner-id :hamburglar}}]]
:post [[1 {:refs {:topic :tl0}}]
[1 {:refs {:topic :tl1}}]]}))
Copy link