Question

I've just been playing with Compojure recently, and I've got a small basic webapp. For my HTML templates I'm using Enlive, and I've got a namespace that holds all the simple, static pages. The defroute call for these pages looks like this:

(defroutes public-routes
  (GET "/" []
    (info/index-template))
  (GET "/about" []
    (info/about-template))
  (GET "/contact" []
    (info/contact-template)))

I've actually got a few more than that, but that should give the idea of what I'm doing.

Now, I thought, that's really just bunch of repetition on my part, so I thought I would try the following:

(defroutes info-routes
  (map #(GET (str "/" %) [] (ns-resolve 'webapp.pages.info
                                        (symbol (str % "-template"))))
       '("about" "contact")))

Of course, this doesn't work, as the map is returning a lazy sequence and not a body (?) of functions. Does someone know what I need to do to get this idea to work?

Or should I be using a completely different approach to cut down on repeating myself?

Was it helpful?

Solution

You can always use the routes function which is used by defroutes:

(defroutes info-routes
  (apply routes
    (map #(GET (str "/" %) [] 
               (ns-resolve 'webapp.pages.info
                           (symbol (str % "-template"))))
         '("about" "contact"))))

But that's still quite boring, let's spice it up! ;-)

(defn templates-for [& nss]
  (->> nss
       (map ns-publics)
       (apply concat)
       (filter #(->> % first str
                     (re-seq #"-template$")))
       (map second)))

(defn template-uri [template]
  (->> template meta :name name
       (re-seq  #"(.*)-template$")
       first second (str "/")))

(defn template->route [template]
  (GET (template-uri template) [] template))

(defroutes public-routes
  (GET "/" [] "foo")
  (apply routes (map template->route
                     (templates-for 'webapp.pages.info))))

With this code, the templates-for function will look for any functions finishing with "-template" in the namespaces given and write the appropriate route with them. Look how I'm not using any macro, but plenty of composition.

OTHER TIPS

defroutes is a macro so unfortunately you wont be able to pass it to a function like map. You would need to write a macro that expanded into a call to defroutes. or look at the functions that it expands into and call them directly.

It wont work to create a list of routes within the call to defroutes like this

(defroutes public-routes
  (make-list-of-routes)

would expand into a list of routes:

(defroutes public-routes
  ( (GET "/" [] (info/index-template)) 
    (GET "/about" [] (info/about-template))
    (GET "/contact" [] (info/contact-template))) )

if defroutes where a normal function you would solve this with apply

(apply defroutes (conj 'public-routes (make-list-of-routes)))

because defroutes is a macro it is completely finished before apply could run and the results would not make a lot of sense. You really can't compose macroes as functions. macroes are not first class citizens in clojure (or any lisp i know of) When some Clojurians (not usually me) say "Macroes are evil" they often think about situations like this where you run into the fact that something is a macro when you try to compose it and can't.

the solution is to not use the defroutes macro and call the routes function directly.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top