Defining the Routes with Compojure

Compojure is a routing library built on top of Ring. It provides an easy way to associate handler functions with a URL and an HTTP method. A Compojure route might look like this:

 
(GET ​"/:id"​ [id] (​str​ ​"<p>the id is: "​ id ​"</p>"​ ))

The route name maps to an HTTP method name, such as GET, POST, PUT, DELETE, or HEAD. There’s also a route called ANY that matches any method the client supplies. The URI can contain keys denoted by using a colon, and their values can be used as parameters to the route. This feature was inspired by a similar mechanism used in Rails and Sinatra.[27][28] The route’s response will be automatically wrapped in the Ring response described earlier.

Since we’re likely to have more than a single route in our application, Compojure provides the routes function that creates a Ring handler from multiple routes. For example, if we had routes /all-items and item/:id, then we could combine these into a single handler as follows:

 
(​defn​ foo-handler []
 
"foo called"​)
 
 
(​defn​ bar-handler [id]
 
(​str​ ​"bar called, id is: "​ id))
 
 
(​def​ handler
 
(routes
 
(GET ​"/all-items"​ [] (foo-handler))
 
(GET ​"/item/:id"​ [id] (bar-handler id))))

Since defining routes is a very common operation, Compojure also provides the defroutes macro that generates a Ring handler from the supplied routes:

 
(defroutes handler
 
(GET ​"/foo"​ [] (foo-handler))
 
(GET ​"/bar/:id"​ [id] (bar-handler id)))

Using Compojure routes, we can easily map functionality to each URL of our site, and provide much of the core functionality needed in a web application. We can then group these routes together using the defroutes macro as we did previously. Compojure, in turn, takes care of creating the Ring handlers for us.

Compojure also provides a powerful mechanism for filtering out common routes in the application based on the shared path elements. Let’s say we have several routes that handle operations for a specific user:

 
(​defn​ display-profile [id]
 
;;TODO: display user profile
 
)
 
(​defn​ display-settings [id]
 
;;TODO: display user account settings
 
)
 
(​defn​ change-password [id]
 
;;TODO: display the page for setting a new password
 
)
 
(defroutes user-routes
 
(GET ​"/user/:id/profile"​ [id] (display-profile id))
 
(GET ​"/user/:id/settings"​ [id] (display-settings id))
 
(GET ​"/user/:id/change-password"​ [id] (change-password-page id))

There’s a lot of repetition in that code, where each route starts with the /user/:id segment. We can use the context macro to factor out the common portion of these routes:

 
(​def​ user-routes
 
(context ​"/user/:id"​ [id]
 
(GET ​"/profile"​ [] (display-profile id))
 
(GET ​"/settings"​ [] (display-settings id))
 
(GET ​"/change-password"​ [] (change-password-page id))))

In that code the routes defined in the context of /user/:id will behave exactly the same as the previous version and have access to the id parameter. The context macro exploits the fact that handlers are closures. When the outer context handler closes over the common parameters, they are also available to handlers defined inside it.

Accessing Request Parameters

For some routes, we’ll need to access the request map to access the request parameters. We do this by declaring the map as the second argument to the route.

 
(GET ​"/foo"​ request (​interpose​ ​", "​ (​keys​ request)))

That route reads out all the keys from the request map and displays them. The output will look like the following.

 
:ssl-client-cert, :remote-addr, :scheme, :query-params, :session, :form-params,
 
:multipart-params, :request-method, :query-string, :route-params, :content-type,
 
:cookies, :uri, :server-name, :params, :headers, :content-length, :server-port,
 
:character-encoding, :body, :flash

Compojure also provides some useful functionality for handling the request maps and the form parameters. For example, in the guestbook application, which we created in Chapter 1, Getting Your Feet Wet, we saw the following route defined:

 
(POST ​"/"​ [​name​ message] (save-message ​name​ message))

This route extracts the :name and :message keys from the request params, then binds them to variables of the same name. We can now use them as any other declared variable within the route’s scope.

It’s also possible to use the regular Clojure destructuring inside the route. Given a request map containing the following parameters…

 
{:params {​"name"​ ​"some value"​}}

…we can extract the parameter with the key "name" as follows:

 
(GET ​"/:foo"​ {{value ​"name"​} :params}
 
(​str​ ​"The value of name is "​ value))

Furthermore, Compojure lets you destructure a subset of form parameters and create a map from the rest:

 
[x y & z]
 
x ​->​ ​"foo"
 
y ​->​ ​"bar"
 
z ​->​ {:v ​"baz"​, :w ​"qux"​}

In the preceding code, parameters x and y have been bound to variables, while parameters v and w remain in a map called z. Finally, if we need to get at the complete request along with the parameters, we can do the following:

 
(GET ​"/"​ [x y :as r] (​str​ x y r))

Here we bind the form parameters x and y, and bind the complete request map to the variable r.

Armed with the functionality that Ring and Compojure provide, we can easily create pages and routes for our site. However, any nontrivial application requires many other features, such as page templating, session management, and input validation. For these tasks we’ll use the libraries best adapted for each task.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.186.6