Search code examples
clojureroutescompojurering

More readable Compojure routes


I'm working on a small Ring / Compojure webapp, and I'm wondering if there is a way to declutter the routes. I'm using standard Clojure destructuring, but need quite a few arguments passed the the function.

This is OK, using the Compojure Compojure-specific destructuring:

(POST "/login/" [email password] (login-post email password)) ; handle login attempt

Now it starts to get worse. I need to give the function both a flash message and en email stored in a session:

(GET "/login/" {flash :flash {email :email} :session} (login-get flash email))

And here are the route for submitting data through a form:

(POST "/" {{user-email :user-email} :session {title :title} :params {tags :tags} :params { content :content } :params { privacy :privacy } :params} (home-post user-email title tags content privacy))

I know i could just send the raw request to then home-post function using :params, but i somehow feel that that placing the parameter extraction with the routes it a better solution. It make the home-post more pure and easier to test. Instread of just supplying every function with a giant request map.

Can the deconstructing in the routes definition be clearer (more readable), fx using some sort of extract-from-map function, macro, anything?

Pro's and con's for placing the destructuring with the routes?


Solution

  • First off, I agree with your assessment that the request destructuring usually belongs with the route handler definitions. The very fact that Compojure provides an extension of Clojure's destructuring syntax seems to indicate that the author also felt similarly.

    There are a couple of things that you can do to reduce duplication in your destructuring forms. First, Clojure's standard map binding destructuring will help with the last route you posted. The first thing that you can do is consolidate the keys that you are pulling from the request :params:

    (POST "/" {{title :title, tags :tags, content :content, privacy :privacy} :params ...} ...)
    

    That is a little clearer, but since you are not renaming any of the values that you are pulling out of :params, you can do better:

    (POST "/" {{:keys [title tags content privacy]} :params ...} ...)
    

    That is about as far as the Clojure destructuring syntax will get you, but you can take advantage of Compojure's additional destructuring syntax to DRY this up even more. Compojure assumes that you will most often be interested in getting values from the :params key of the request, so you can use vector destructuring to pull them out:

    (POST "/" [title tags content privacy] ...)
    

    Much better. Since you still need to get additional values out of the request, you can supply an :as the-request at the end of the binding vector to access the request directly:

    (POST "/" [title tags content privacy :as req] ...)
    

    Keep in mind, however, that this variable can also be destructured. With all of this in mind, here is a simplified version of your last route:

    (POST "/" [title tags content privacy :as {{user-email :user-email} :session}]
      (home-post user-email title tags content privacy))
    

    I hope that this is helpful! The more Clojure you write, the more ways you can find of simplifying code to make it clearer and more concise.