Hi All,

In an attempt to understand the nav part of the new datafy/nav protocols 
I've created an example project (found here) 
<https://github.com/markbastian/datafy-playground> that I'd like to share 
and get your feedback on to see if I'm doing it right. I'll inline the code 
as well.

For reference, I've been reading Sean Corfield's blog post 
<http://corfield.org/blog/2018/12/03/datafy-nav/> and watching Stuart 
Halloway's REBL <https://www.youtube.com/watch?v=c52QhiXsmyI> talk to 
understand these protocols. My goal is to create a complete but minimal 
working example (with both data to work with as well as an implementation 
of nav).

The code should be pretty self-explanatory and I've inlined my questions in 
the parts that I'm still fuzzy on. I'll ask them here so they're together:

   1. Should datafication always occur prior to navigation? In other words, 
   if your API already exposes plain old data should you require an explicit 
   datafication step? One argument I could see in favor of this is that the 
   plain old data does not have the nav metadata attached and this may still 
   be required.
   2. Is it OK to add "partial data" keys as I've done with the :children 
   and :parents relationships? Stuart seems to do this here 
   <https://youtu.be/c52QhiXsmyI?t=967>with respect to file contents and 
   perhaps that's exactly what he means when he discusses deferred navigation 
   here <https://youtu.be/c52QhiXsmyI?t=1524>. When you do this sort of 
   thing (add a "hint" or link to something else), is there a canonical way to 
   represent the value? For a DB with a foreign key or a web link this makes 
   sense. The ID and url are the values. For other things it isn't as cut and 
   dried.
   3. Not a question, but I welcome any other feedback, especially "Yes, 
   this is right" or "No, you got this wrong."

Please let me know if I've got it right or if you have additional insights 
into these protocols (especially nav). If anyone finds this useful, feel 
free to use it in any way you want.

(ns datafy-playground.datafy-sqldb
  (:require [clojure.java.jdbc :as j]
            [clojure.core.protocols :as p]
            [clojure.datafy :refer [datafy nav]]
            [clojure.pprint :as pp]))

(declare person)

(def person-def
  (j/create-table-ddl
    :person
    [[:id :int "not null"]
     [:name "varchar(32)"]
     [:age :int "not null"]
     [:spouse_id :int]]))

(def children-def
  (j/create-table-ddl
    :children
    [[:parent_id :int]
     [:child_id :int]]))

(defn enhance [db m]
  (with-meta
    m
    {`p/datafy (fn [m]
                 (with-meta
                   m
                   {`p/nav (fn [_ k v]
                             (case k
                               :parent_id (person db v)
                               :child_id (person db v)
                               v))}))}))

(defn children-of [db id]
  (->> (j/query db [(format "SELECT * FROM children WHERE parent_id = %s" id)])
       (map (partial enhance db))))

(defn parents-of [db id]
  (->> (j/query db [(format "SELECT * FROM children WHERE child_id = %s" id)])
       (map (partial enhance db))))

(defn person [db id]
  (let [[m] (j/query db [(format "SELECT * FROM person WHERE id = %s" id)])]
    (with-meta
      m
      {`p/datafy (fn [m]
                   (with-meta
                     (into m {:children :... :parents :...})
                     {`p/nav (fn [{:keys [id]} k v]
                               (case k
                                 :parents (parents-of db id)
                                 :children (children-of db id)
                                 :spouse_id (person db v)
                                 v))}))})))

(defn with-sample-db [dbfn]
  ;See http://www.h2database.com/html/features.html#database_url for connection 
string info
  (with-open [c (j/get-connection {:connection-uri "jdbc:h2:mem:test_mem"})]
    (let [db {:connection c}]
      (j/db-do-commands db person-def)
      (j/db-do-commands db children-def)
      (j/insert-multi! db :person [{:id 1 :name "Richard Parker" :age 62 
:spouse_id 2}
                                   {:id 2 :name "Mary Parker" :age 63 
:spouse_id 1}
                                   {:id 3 :name "Ben Parker" :age 65 :spouse_id 
4}
                                   {:id 4 :name "May Parker" :age 64 :spouse_id 
3}
                                   {:id 5 :name "Peter Parker" :age 26}
                                   {:id 6 :name "Mary Jane Watson" :age 26}])
      (j/insert-multi! db :children [{:parent_id 1 :child_id 5}
                                     {:parent_id 2 :child_id 5}])
      (do (dbfn db)))))

(comment
  ;Example 1a: Note that prior to datafication nav just returns the value 2
  (with-sample-db
    (fn [db]
      (-> (person db 1)
          (nav :spouse_id 2))))

  ;Example 1b: datafication works
  (with-sample-db
    (fn [db]
      (-> (person db 1)
          ;Datafication now converts from "Thing" to navigable thing. In this 
case our thing is just
          ; a map so it doesn't look any different. Does it make sense to 
require explicit datafication here?
          datafy
          ;And now we can nav to the spouse of person 1
          (nav :spouse_id 2))))

  ;Example 2: Getting the children. Is this a valid case of datafy/nav or is 
this an abuse of the API?
  (with-sample-db
    (fn [db]
      (-> (person db 1)
          ;Note that datafication adds the children and parents fields, but the 
values are ..., implying
          ;that we haven't evaluated this yet. Is there a better value to use? 
nil would lead the user to
          ;believe that we know the children/parents and they are nil.
          datafy)))

  (with-sample-db
    (fn [db]
      (-> (person db 1)
          datafy
          ;I can now nav to my children
          (nav :children :...)
          ;And browse them as regular data
          first
          ;But I can't keep navigating because I haven't datafied
          (nav :child_id 5))))

  (with-sample-db
    (fn [db]
      (->
        ;Get a thing
        (person db 1)
        ;Datafy it
        datafy
        ;nav (is this legit?)
        (nav :children :...)
        ;browse as data
        first
        ;datafy - as before, is explicit datafication needed?
        datafy
        ;nav
        (nav :child_id 5)
        ;And we get our answer (Peter Parker)
        pp/pprint)))
  )


Thanks,
Mark

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to