-
-
Notifications
You must be signed in to change notification settings - Fork 8
/
default_fields.clj
123 lines (106 loc) · 5.67 KB
/
default_fields.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
(ns toucan2.tools.default-fields
(:require
[clojure.spec.alpha :as s]
[methodical.core :as m]
[toucan2.log :as log]
[toucan2.map-backend.honeysql2 :as map.honeysql]
[toucan2.pipeline :as pipeline]
[toucan2.util :as u]))
(set! *warn-on-reflection* true)
(s/def ::default-field
(s/or :keyword keyword?
:fn-and-alias (s/spec (s/cat :fn ifn?
:keyword keyword?))))
(s/def ::default-fields
(s/coll-of ::default-field))
(m/defmulti default-fields
"The default fields to return for a model `model` that derives from `:toucan2.tools.default-fields/default-fields`. You
probably don't need to use this directly; use [[toucan2.tools.default-fields/define-default-fields]] instead."
{:arglists '([model]), :defmethod-arities #{1}, :dispatch-value-spec (complement vector?)}
u/dispatch-on-first-arg)
(m/defmethod default-fields :around :default
[model]
(let [fields (next-method model)]
(when (s/invalid? (s/conform ::default-fields fields))
(throw (ex-info (format "Invalid default fields for %s: %s" (pr-str model) (s/explain-str ::default-fields fields))
(s/explain-data ::default-fields fields))))
(log/debugf :results "Default fields for %s: %s" model fields)
fields))
(defn- default-fields-xform [model]
(let [field-fns (mapv (fn [[field-type v]]
(case field-type
:keyword (fn [instance]
[v (get instance v)])
:fn-and-alias (let [{k :keyword, f :fn} v]
(fn [instance]
[k (f instance)]))))
(s/conform ::default-fields (default-fields model)))]
(map (fn [instance]
(log/tracef :results "Selecting default-fields from instance")
(into (empty instance) (map (fn [field-fn]
(field-fn instance))
field-fns))))))
(def ^:dynamic *skip-default-fields*
"Whether to skip applying default Fields because the query already includes explicit fields, e.g. `:select` for a Honey
SQL query."
false)
;;; TODO -- should we skip default fields for a Query that has top-level `:union` or `:union-all`?
(m/defmethod pipeline/transduce-query [#_query-type :toucan.result-type/instances
#_model ::default-fields
#_resolved-query-type :toucan.map-backend/honeysql2]
"Skip default fields behavior for Honey SQL queries that contain `:select`. Bind [[*skip-default-fields*]] to `true`."
[rf query-type model parsed-args honeysql]
(if (map.honeysql/include-default-select? honeysql)
(next-method rf query-type model parsed-args honeysql)
(binding [*skip-default-fields* true]
(log/debugf :results "Not adding default fields because query already contains `:select` or `:select-distinct`")
(next-method rf query-type model parsed-args honeysql))))
(m/defmethod pipeline/results-transform [#_query-type :toucan.result-type/instances #_model ::default-fields]
[query-type model]
(log/debugf :results "Model %s has default fields" model)
(cond
*skip-default-fields*
(next-method query-type model)
;; don't apply default fields for queries that specify other columns e.g. `(select [SomeModel :col])`
(seq (:columns pipeline/*parsed-args*))
(do
(log/debugf :results "Not adding default fields transducer since query already has `:columns`")
(next-method query-type model))
;; don't apply default fields for queries like [[toucan2.select/select-fn-set]] since they are already doing their
;; own transforms
(isa? query-type :toucan.query-type/select.instances.fns)
(do
(log/debugf :results "Not adding default fields transducer since query type derives from :toucan.query-type/select.instances.fns")
(next-method query-type model))
;; don't apply default fields for the recursive select done by before-update, because it busts things when we want
;; to update non-default fields =(
;;
;; See [[toucan2.tools.before-update-test/before-update-with-default-fields-test]]
(isa? query-type :toucan2.tools.before-update/select-for-before-update)
(do
(log/debugf :results "Not adding default fields transducer since query type is done for the purposes of before-update")
(next-method query-type model))
:else
(do
(log/debugf :results "adding transducer to return default fields for %s" model)
(let [xform (default-fields-xform model)]
(comp xform
(next-method query-type model))))))
;;; `default-fields` should be done before [[toucan2.tools.after-select]] so that fields added by after select get
;;; preserved.
(m/prefer-method! #'pipeline/results-transform
[:toucan.result-type/instances ::default-fields]
[:toucan.result-type/instances :toucan2.tools.after-select/after-select])
;;; `default-fields` should be done before [[toucan2.tools.after-update]] and [[toucan2.tools.after-insert]] so that
;;; fields added by those methods get preserved.
(m/prefer-method! #'pipeline/results-transform
[:toucan.result-type/instances ::default-fields]
[:toucan.result-type/instances :toucan2.tools.after/model])
(defmacro define-default-fields {:style/indent :defn} [model & body]
`(let [model# ~model]
(u/maybe-derive model# ::default-fields)
(m/defmethod default-fields model# [~'&model] ~@body)))
(s/fdef define-default-fields
:args (s/cat :model some?
:body (s/+ any?))
:ret any?)