Om Next 的 query->ast 和 ast->query 函数
Om Next's query->ast and ast->query functions
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.
问题:为什么需要这些功能?也就是说,为什么需要在 om next 中直接操作查询抽象语法树(我假设它是代表查询树的 clojure 映射以及一些元数据)?
有些场景需要直接操作查询ast。在远程解析模式下,解析器希望您的读取函数 return {:remote-name true } 或(可能已修改){:remote-name AST-node}(它来自如 :ast 在环境中)。大多数情况下,您必须修改 AST 以重组它或添加一些数据。
示例 1:
您有一个问题:[{:widget {:list [:name :created]}}]
:widget
部分是纯 UI 相关的,您的服务器不需要知道它的存在,它只 cares/knows 关于 :list
。
基本上你必须在解析器中修改 AST:
(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)}))
如果您在发送函数中使用 om/process-roots
,它将从 ast 中提取 :query-root
并将查询从 [{:widget {:list [:name :created]}}]
重写为 [{:list [:name :created]}]
。
示例 2:
另一个例子是当你想在远程改变某些东西时:
(defmethod mutate 'item/update
[{:keys [state ast]} key {:keys [id title]}]
{:remote (assoc ast :params {:data {:id id :title title })})
这里你需要明确告诉Om在AST中包含你要发送的数据。然后在您的遥控器上选择 :data
以更新给定 id
的标题
大多数时候您不会直接使用您在问题中描述的功能。解析器的每个方法中可用的 env
中都有 ast
。
我在尝试使用 Compassus 时偶然发现的事情:
假设您有一个包含参数子查询的复杂 union/join 查询。像这样:
`[({:foo/info
{:foo/header [:foo-id :name]
:foo/details [:id :description :title]}} {:foo-id ~'?foo-id
:foo-desc ~'?foo-desc})]
现在假设您要设置参数,以便在服务器上您可以使用 om/parser 解析它并将这些参数视为 read
调度的第三个参数。当然,可以编写一个函数来查找查询中所有必需的参数并设置值。但这并不容易,正如我所说 - 想象一下您的查询可能会非常复杂。
那么您可以做的是修改 ast,ast 包含 :children :params
键。因此,假设 :foo-id
和 :foo-desc
的实际值位于 :route-params
键下的状态原子中:
(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'}))
所以基本上你是:
- 抓住 ast
- 用实际值修改它
您认为也许您可以立即将其发送到服务器。唉,不!还没有。事情是 - 当你做 {:remote ast}
时,Om 获取 ast 的 :query
部分,从中组成 ast,然后将它发送到服务器。因此,您实际上需要:将修改后的 ast 转换为查询,然后再将其转换回 ast。
备注:
本例中的 set-ast-params
函数仅适用于第一级(如果您有嵌套的参数化查询 - 它不会起作用),
让它递归 - 不难
有两种不同的方法可以将 ast 转换为查询,反之亦然:
(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)
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.
问题:为什么需要这些功能?也就是说,为什么需要在 om next 中直接操作查询抽象语法树(我假设它是代表查询树的 clojure 映射以及一些元数据)?
有些场景需要直接操作查询ast。在远程解析模式下,解析器希望您的读取函数 return {:remote-name true } 或(可能已修改){:remote-name AST-node}(它来自如 :ast 在环境中)。大多数情况下,您必须修改 AST 以重组它或添加一些数据。
示例 1:
您有一个问题:[{:widget {:list [:name :created]}}]
:widget
部分是纯 UI 相关的,您的服务器不需要知道它的存在,它只 cares/knows 关于 :list
。
基本上你必须在解析器中修改 AST:
(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)}))
如果您在发送函数中使用 om/process-roots
,它将从 ast 中提取 :query-root
并将查询从 [{:widget {:list [:name :created]}}]
重写为 [{:list [:name :created]}]
。
示例 2: 另一个例子是当你想在远程改变某些东西时:
(defmethod mutate 'item/update
[{:keys [state ast]} key {:keys [id title]}]
{:remote (assoc ast :params {:data {:id id :title title })})
这里你需要明确告诉Om在AST中包含你要发送的数据。然后在您的遥控器上选择 :data
以更新给定 id
大多数时候您不会直接使用您在问题中描述的功能。解析器的每个方法中可用的 env
中都有 ast
。
我在尝试使用 Compassus 时偶然发现的事情:
假设您有一个包含参数子查询的复杂 union/join 查询。像这样:
`[({:foo/info
{:foo/header [:foo-id :name]
:foo/details [:id :description :title]}} {:foo-id ~'?foo-id
:foo-desc ~'?foo-desc})]
现在假设您要设置参数,以便在服务器上您可以使用 om/parser 解析它并将这些参数视为 read
调度的第三个参数。当然,可以编写一个函数来查找查询中所有必需的参数并设置值。但这并不容易,正如我所说 - 想象一下您的查询可能会非常复杂。
那么您可以做的是修改 ast,ast 包含 :children :params
键。因此,假设 :foo-id
和 :foo-desc
的实际值位于 :route-params
键下的状态原子中:
(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'}))
所以基本上你是:
- 抓住 ast
- 用实际值修改它
您认为也许您可以立即将其发送到服务器。唉,不!还没有。事情是 - 当你做 {:remote ast}
时,Om 获取 ast 的 :query
部分,从中组成 ast,然后将它发送到服务器。因此,您实际上需要:将修改后的 ast 转换为查询,然后再将其转换回 ast。
备注:
-
本例中的
set-ast-params
函数仅适用于第一级(如果您有嵌套的参数化查询 - 它不会起作用), 让它递归 - 不难有两种不同的方法可以将 ast 转换为查询,反之亦然:
(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)