14 Nov 2012
defhandler, a clojure macro aid define ring handler
Currently, I am working at AVOS Systems’s China team. I am in a group responsible for building meiweisq, The Delicious web service, but for Chinese users, and using Clojure!
We are using Ring and Compojure. Ring’s request/response abstraction is great, Compojure’s defroutes
family functions get its job done perfectly. We are just happy using it.
A ring handler is just a plain Clojure function, passing a request map, return a response map:
(defn handler [req]
;; return a map of keys [:status :body :headers]
)
There are some boilerplate code, in this pattern:
;;; get the params
(let [{:keys [id tag]} (:params req)
limit (min 30 (to-int (or (:limit (:params req)) 15)))
offset (to-int (or (:offset (:params req)) 0))]
;; do work with these params
)
They are repeated in the source code. Repeats are bad. Macro comes to the rescue:
(defmacro defhandler [handler bindings & body]
(let [req (bindings 0)
bindings (rest bindings)
ks (map (fn [s] (keyword (name s))) bindings)
vals (map (fn [k]
;; limit and offset are special, set the default value, guard large
;; limit, convert them to int
(cond (= :limit k) `(min 30 (to-int (or (~k (:params ~req)) 15)))
(= :offset k) `(to-int (or (~k (:params ~req)) 0))
;; more pattens here, something like
;; (= :uid k) code to get user id
:else `(~k (:params ~req)))) ks)]
`(defn ~handler [~req]
(let [~@(interleave bindings vals)]
~@body))))
example usage
(defhandler list-feeds [req id limit offset]
{:status 200
:body (str "the subscription id: " id
"limit:" limit
"offset:" offset)})
Macroexpand to
(defn list-feeds [req]
(let [id (:id (:params req))
limit (min 30 (to-int (or (:limit (:params req)) 15)))
offset (to-int (or (:offset (:params req)) 0))]
{:status 200
:body (str "the subscription id: " id
"limit:" limit
"offset:" offset)}))
Boilerplate killed. I am happy now.
By the way, to-int
source code:
(defn to-int [s] (cond
(string? s) (Integer/parseInt s)
(instance? Integer s) s
(instance? Long s) (.intValue ^Long s)
:else 0))