Om Next's query->ast and ast->query functions
Asked Answered
Y

2

6

According to Om Next's documentation:

query->ast

(om.next/query->ast '[(:foo {:bar 1})])

Given a query expression return the AST.

ast->query

(om.next/ast->query ast)

Given a query expression AST, unparse it into a query expression.

Question: Why would one need these functions? That is, why would one need to directly manipulate a query abstract syntax tree (which I'm assuming are clojure maps that represent a query tree, along with some meta data) in om next?

Yahairayahata answered 27/2, 2016 at 21:38 Comment(0)
L
7

There are some scenarios where you need to manipulate the query ast directly. In remote parsing mode, the parser expects your read functions to return either {:remote-name true } or a (possibly modified) {:remote-name AST-node} (which comes in as :ast in env). Most often you'll have to modify the AST to restructure it or add some data.

Example 1: You have a query: [{:widget {:list [:name :created]}}] The :widget part is pure UI related, your server doesn't need to know it exists, it only cares/knows about the :list. Basically you'll have to modify the AST in the parser:

(defmethod read :list
  [{:keys [ast query state]} key _ ]
  (let [st @state]
    {:value (om/db->tree query (get st key) st)
     :remote (assoc ast :query-root true)}))

If you use om/process-rootsin your send function, it'll pick up the :query-root out of the ast and rewrite the query from [{:widget {:list [:name :created]}}] to [{:list [:name :created]}].

Example 2: Another example would be when you want to mutate something at a remote:

(defmethod mutate 'item/update
  [{:keys [state ast]} key {:keys [id title]}]
  {:remote (assoc ast :params {:data {:id id :title title })})

Here you need to explicitly tell Om to include the data you want to send in the AST. At your remote you then pick apart :data to update the title at the given id

Most of the time you won't use the functions you described in your questions directly. The env available in every method of the parser has the ast in it.

Lawrencelawrencium answered 28/2, 2016 at 9:26 Comment(4)
Just a note: You never used any of the functions that George is asking for :)Foreboding
@Andre: this answer was still extremely informative! Also, he said most of the time these functions won't be used much in practice.Yahairayahata
Does a :remote key in a reader function always have to have a query ast as its value? Or can it merely return a raw query (i.e., something of form[ ... ] using Datomic pull syntax)?Yahairayahata
It's either an ast or just a boolean true to mark it for a certain remote. In the case of an ast, it has to be an ast, not a query. You could however use (om/query->ast) if you want to modify the query instead of an ast directly, before sending off that ast. Just keep in mind the end-result has to be a valid ast. Once you get the hang of it, modifying the ast from the env is just like drinking water or breathing air ;)Lawrencelawrencium
P
2

Something I stumbled on, while trying to use Compassus:

Let's say you have a complex union/join query that includes parametric sub-queries. Something like this:

`[({:foo/info
        {:foo/header [:foo-id :name]
        :foo/details [:id :description :title]}} {:foo-id ~'?foo-id
                                                  :foo-desc ~'?foo-desc})]

Now let's say you want to set parameters so on the server you can parse it with om/parser and see those params as 3rd argument of read dispatch. Of course it's possible to write a function that would find all necessary parameters in the query and set the values. That's not easy though, and as I said - imagine your queries can be quite complex.

So what you can do - is to modify ast, ast includes :children :params key. So let's say the actual values for :foo-id and :foo-desc are in the state atom under :route-params key:

(defn set-ast-params [children params]
  "traverses given vector of `children' in an AST and sets `params`"
  (mapv
    (fn [c]
      (let [ks (clojure.set/intersection (-> params keys set) 
                                         (-> c :params keys set))]
        (update-in c [:params] #(merge % (select-keys params (vec ks))))))
    children))

(defmethod readf :foo/info
  [{:keys [state query ast] :as env} k params]
  (let [{:keys [route-params] :as st} @state
        ast' (-> ast 
                 (update :children #(set-ast-params % route-params))
                 om/ast->query
                 om.next.impl.parser/expr->ast)]
    {:value  (get st k)
    :remote ast'}))

So basically you are: - grabbing ast - modifying it with actual values you think maybe you can send it to server right then. Alas, no! Not yet. Thing is - when you do {:remote ast}, Om takes :query part of the ast, composes ast out of it and then sends it to the server. So you actually need to: turn your modified ast into query and then convert it back to ast again.

Notes:

  • set-ast-params function in this example would only work for the first level (if you have nested parametrized queries - it won't work), make it recursive - it's not difficult

  • there are two different ways to turn ast to query and vice-versa:

    (om/ast->query) ;; retrieves query from ast and sets the params based 
                    ;; of `:params` key of the ast, BUT. it modifies the query, 
                    ;; if you have a join query it takes only the first item in it. e.g. :
    [({:foo/foo [:id]
        :bar/bar [:id]} {:id ~'?id})]
    ;; will lose its `:bar` part 
    
    (om.next.impl.parser/ast->expr) ;; retrieves query from an ast, 
                                    ;; but doesn't set query params based on `:params` keys of the ast.
    
    ;; there are also
    (om/query->ast) ;; and
    (om.next.impl.parser/expr->ast)
    
Pharmacopsychosis answered 22/10, 2016 at 0:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.