3

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?

2 Answers2

1

So the issue I'm having is one of understanding I think, António Monteiro in the Om Slack channel suggested I use the db->tree function. Using this in the :list/events multi-method let's it return the whole tree of de-normalised data.

0

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.

Chris Murphy
  • 6,411
  • 1
  • 24
  • 42
  • My misunderstanding was where the recursion happened. My thinking was that the parser would encounter the second level join and look for the relevant multi-method, hence why I had a multi-method on `:body`. So given what you're saying you'll only have `read` multi-methods for each top level key, right? – Donavan Costaras Mar 27 '16 at 12:57
  • My understanding is that for the keys right at the bottom you don't need read methods, but for all levels higher up you do. In reality its not too difficult because you get an error message and put another one in! – Chris Murphy Mar 27 '16 at 13:03
  • I understand the default db format and can get de-normalised data into it correctly with composed `IQuery` and `Ident` static methods. I understand now that I need to do the recursive join within the top level `read` multi-method and that the parser will not call further multi-methods. Given that, it seems to me that then you'll only need top-level `read` multi-methods. So I don't understand it when you say "but for all levels higher up you do". It's time to read the source I think, hah! – Donavan Costaras Mar 27 '16 at 13:16
  • I think you are correct. `db->tree` has all it needs with the query and the app db. It recursively follows the idents. The only time a multi-method will be needed is when a key is encountered in the query, that itself has a query. – Chris Murphy Mar 27 '16 at 14:33
  • Wow, Untangled was just what the doctor ordered! Thanks for the link. – Donavan Costaras Mar 27 '16 at 16:16