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.