Search code examples
clojurescriptom-next

Normalization and Idents in Om/Next


I’m trying to get my head around normalization and thought I was making progress, but I’ve stumbled again and am not sure if I’m just not thinking correctly about the problem. How do I normalize the current user’s messages?

(def init-data
  {:session {:user/id 1
             :messages [{:message/id 1}]}
   :messages [{:message/id 1 :text "Message 1"}
              {:message/id 2 :text "Message 1"}]
   :users [{:user/id 1 :email "1@foo.com"}
           {:user/id 2 :email "2@foo.com"}]})

(defui Message
  static om/Ident
  (ident [this {:keys [message/id]}]
    [:message/by-id id])
  static om/IQuery
  (query [this]
    [:id]))

(defui User
  static om/Ident
  (ident [this {:keys [user/id]}]
         [:user/by-id id])
  static om/IQuery
  (query [this]
         `[:id {:properties ~(om/get-query Property)}]))

(defui Session
  static om/Ident
  (ident [this {:keys [user/id]}]
         [:user/by-id id])
  static om/IQuery
  (query [this]
         [:id]))

(defui RootView
  static om/IQuery
  (query [this]
    (let [message-query (om/get-query Message)
          user-query (om/get-query User)
          session-query (om/get-query Session)]
     `[{:messages ~message-query}
       {:users ~user-query}
       {:session ~session-query}])))

=> (def norm-data (om/tree->db RootView init-data true))
=> (pp/pprint norm-data)

{:session [:user/by-id 1],
 :messages [[:message/by-id 1] [:message/by-id 2]],
 :users [[:user/by-id 1] [:user/by-id 2]],
 :message/by-id
 {1 {:message/id 1, :text "Message 1"},
  2 {:message/id 2, :text "Message 1"}},
 :user/by-id
 {1 {:user/id 1, :email "1@foo.com", :messages [{:message/id 1}]},
  2 {:user/id 2, :email "2@foo.com"}},
 :om.next/tables #{:message/by-id :user/by-id}}

Solution

  • I changed your initial data a little and managed to get tree->db to get us to a sensible looking default-db-format, where Idents are ubiquitous:

    {:app/session [:session/by-id 1],
     :app/messages [[:message/by-id 100] [:message/by-id 101]],
     :app/users [[:user/by-id 200] [:user/by-id 201]],
     :message/by-id
                {100 {:id 100, :text "Message 1"}, 101 {:id 101, :text "Message 2"}},
     :user/by-id
                {200 {:id 200, :email "1@foo.com"},
                 201 {:id 201, :email "2@foo.com"}},
     :session/by-id {1 {:id 1, :app/messages [[:message/by-id 100]]}}}
    

    The components:

    (defui Message
      static om/Ident
      (ident [this {:keys [id]}]
        [:message/by-id id])
      static om/IQuery
      (query [this]
        [:id :text]))
    
    (defui User
      static om/Ident
      (ident [this {:keys [id]}]
        [:user/by-id id])
      static om/IQuery
      (query [this]
        [:id :email]))
    
    (defui Session
      static om/Ident
      (ident [this {:keys [id]}]
        [:session/by-id id])
      static om/IQuery
      (query [this]
        [:id {:app/messages (om/get-query Message)}]))
    
    (defui RootView
      static om/IQuery
      (query [this]
        [{:app/messages (om/get-query Message)}
         {:app/users (om/get-query User)}
         {:app/session (om/get-query Session)}]))
    

    And the initial data (the input to tree->db):

    (def init-data
      {:app/session {:id 1
                     :app/messages [{:id 100}]}
       :app/messages [{:id 100 :text "Message 1"}
                      {:id 101 :text "Message 2"}]
       :app/users [{:id 200 :email "1@foo.com"}
                   {:id 201 :email "2@foo.com"}]})