Search code examples
clojurescriptom

Om Next read multi-fn not being called in second level join, Query AST not parsed fully, therefore component only receiving idents


I'm having trouble getting a second level join to work correctly. I've elided some things here for brevities sake.

My root component is:

(defui RootView
  static om/IQuery
  (query [this]
    `[{:list/events ~(om/get-query Event)}])
  Object
  (render [this]
    (let [{:keys [list/events]} (om/props this)]
      (events/event-list events))))

My queries compose correctly and the initial data is normalised correctly. I won't show the normalised data and there's more to the total query.

(prn (om/get-query RootView)) =>

[{:list/events
  [:id
   {:body [:id :text :headline]}
   {:media [:id :url :caption :credit]}
   {:start-date [:id :year :month :day]}]}]

If I run a query containing the joins through a parser I get:

(prn (parser {:state (atom norm-data)}
       '[{:list/events
          [:id
           {:body [:id :text :headline]}
           {:media [:id :url :caption :credit]}
           {:start-date [:id :year :month :day]}]}])) =>

{:list/events
 [{:id 1,
   :media [:media/by-id 1],
   :start-date [:start-date/by-id 1],
   :body [:body/by-id 1]}
  {:id 17,
   :media [:media/by-id 17],
   :start-date [:start-date/by-id 17],
   :body [:body/by-id 17]}]}

So the read function for :list/events is called and returns it's data, though all the second joins for :body, :media and :start-date are not.

My read functions are as follows, the second one is the one that is not called. I've left out the multi-methods on :media and :start-date, they also are not called. I'm not sure what this is a symptom of though.

(defmulti read om/dispatch)

(defmethod read :list/events
  [{:keys [state] :as env} key params]
  (let [st @state]
    {:value (into [] (map #(get-in st %)) (get st key))}))

(defmethod read :body
  [{:keys [state query]} key _]
  (println "This is never printed")
  {:value :doesnt-matter})

The join is correctly identified in the AST (so I assume the query grammar is correct) and the dispatch key matches that of the multi-method.

(prn (om/query->ast (om/get-query RootView))) =>

{:type :root,
 :children
 [{:type :join,
   :dispatch-key :list/events,
   :key :list/events,
   :query
   [:id
    {:body [:id :text :headline]}
    {:media [:id :url :caption :credit]}
    {:start-date [:id :year :month :day]}],
   :component timeline.components.events/Event,
   :children
   [{:type :prop, :dispatch-key :id, :key :id}
    {:type :join,
     :dispatch-key :body,
     :key :body,
     :query [:id :text :headline],
     :component timeline.components.events/EventBody,
     :children
     [{:type :prop, :dispatch-key :id, :key :id}
      {:type :prop, :dispatch-key :text, :key :text}
      {:type :prop, :dispatch-key :headline, :key :headline}]}]}]}

I can't understand why the parser or something (?) stops at the second join? As far as my limited understanding goes, the multi-method on :body should at least be called?


Solution

  • You have to do the recursion from within the reads yourself i.e. invoke the parser on the query that is within the key being examined. db->tree does this for you. In fact it is not unusual for every read to call db->tree and so look pretty much the same. In fact because of this Untangled does away with these reads altogether. In which case you really don't have to do the recursion yourself!

    There's no recursion here:

    (into [] (map #(get-in st %)) (get st key)) 
    

    Any get on a key is to the refs part of the default db formatted data (app data). So here a sequence of idents will be returned by (get st key). Any get-in is to the tables part of the app data, and so returns real data values. (map #(get-in st %)) is the transducer that does this for every ident. But the tables part of the data is a recursive data structure - has to be for a lack of repetition - so any data that is not 'leaf' data is represented by an ident. So that's what you are getting back - anything that's one level deep and idents otherwise.

    This answer is going to make next to no sense without an understanding of the default database format - the refs and tables parts. The best explanation I've found so far is here

    This data (st) is in default db format:

    { :list/people [[:people/by-id 1] [:people/by-id 2] ... ]
      :people/by-id { 1 { :db/id 1 :person/name "Joe" :person/mate [:people/by-id 2]}
                      2 { :db/id 2 :person/name "Sally" :person/mate [:people/by-id 1]}}}
    

    Wherever you see by-id that's a give away that the key is in a tables mapentry. As you can see by the structure (get-in st [:people/by-id 1]) will retrieve for you a map that is the real data, of course only to one level deep.

    Here :list/people is a key where the associated value is a vector of idents. (get st :list/people) will give you this vector. This mapentry is the refs part of st.