Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing state as parameter to a ring handler?

How does one inject state into ring handlers most conveniently (without using global vars)?

Here is an example:

(defroutes main-routes
  (GET "/api/fu" [] (rest-of-the-app the-state)))

(def app
  (-> (handler/api main-routes)))

I would like to get the-state into the compojure handler for main-routes. The state might be something like a map created with:

(defn create-app-state []
  {:db (connect-to-db)
   :log (create-log)})

In a non ring application I would create the state in a main function and start injecting it, or parts of it, as function parameters to the different components of the application.

Can something similar be done with ring's :init function without using a global var?

like image 223
4ZM Avatar asked Nov 04 '13 20:11

4ZM


2 Answers

I've seen this done a couple of ways. The first is using middleware that injects the state as a new key in the request map. For instance:

(defroutes main-routes
  (GET "/api/fu" [:as request]
    (rest-of-the-app (:app-state request))))

(defn app-middleware [f state]
  (fn [request]
    (f (assoc request :app-state state))))

(def app
  (-> main-routes
      (app-middleware (create-app-state))
      handler/api))

The other approach is to replace the call to defroutes, which behind the scenes will create a handler and assign it to a var, with a function that will accept some state and then create the routes, injecting the state as parameters to function calls within the route definitions:

(defn app-routes [the-state]
  (compojure.core/routes
    (GET "/api/fu" [] (rest-of-the-app the-state))))

(def app
  (-> (create-app-state)
      app-routes
      api/handler))

Given a choice, I'd probably go with the second approach.

like image 124
Alex Avatar answered Nov 05 '22 04:11

Alex


In addition to what Alex described some routing frameworks for ring have a place for additional arguments which can be accessed by all handlers. In reitit this would work by putting custom objects under :data:

 (reiti.ring/ring-handler
   (reiti.ring/router
    [ ["/api"
      ["/math" {:get {:parameters {:query {:x int?, :y int?}}
                      :responses  {200 {:body {:total pos-int?}}}
                      :handler    (fn [{{{:keys [x y]} :query} :parameters}]
                                    {:status 200
                                     :body   {:total (+ x y)}})}}]] ]
    {:syntax    :bracket
     :exception pretty/exception
     :data      {:your-custom-data your-custom-data
                 :coercion   reitit.coercion.spec/coercion
                 :muuntaja   m/instance
                 :middleware []}}))

In your handler you're supposed to only work with :parameters, but you will be able to access your custom data by selecting :reitit.core/match and :data. The argument that the handler receives is based entirely on this:

(defrecord Match [template data result path-params path])
(defrecord PartialMatch [template data result path-params required])
like image 28
m33lky Avatar answered Nov 05 '22 05:11

m33lky