I just looked at my project again and thought I should mention that I have another clojuregrade.web namespace through which I require the clojuregrade.landing namespace (as landing). I then call the landing/home function through a defroutes action defined in clojuregrade.web. I am using the web.clj file because this is how the lein new heroku command templates the project. I will post the code from that file here with comments. I must admit that I am still trying to figure out how middleware works, so if that is the reason for my continuing 404 error then I would really appreciate any pointers you are willing to give to help me grok that. The official docs are a little confusing for me. In any case, I'll keep researching that now and when I level up I'll be sure to pass the good karma along.
(ns clojuregrade.web
(:use ring.util.response)
(:require [compojure.core :refer [defroutes GET PUT POST DELETE ANY]]
[compojure.handler :refer [site]]
[compojure.route :as route]
[
clojure.java.io :as io]
[ring.middleware.stacktrace :as trace]
[ring.middleware.session :as session]
[ring.middleware.session.cookie :as cookie]
[ring.adapter.jetty :as jetty]
[ring.middleware.basic-authentication :as basic]
[cemerick.drawbridge :as drawbridge]
[environ.core :refer [env]]
[clojuregrade.landing :as landing])) ; <- requiring the external file
(defn- authenticated? [user pass]
;; TODO: heroku config:add REPL_USER=[...] REPL_PASSWORD=[...]
(= [user pass] [(env :repl-user false) (env :repl-password false)]))
(def ^:private drawbridge
(-> (drawbridge/ring-handler)
(session/wrap-session)
(basic/wrap-basic-authentication authenticated?)))
(defroutes app
(ANY "/repl" {:as req}
(drawbridge req)) ;; <- Not sure I really need all this drawbridge/auth stuff or if it is causing a problem by hanging out in my code.
(GET "/" [] (landing/home)) ;; <- callling the home function from clojuregrade.landing
(ANY "*" []
(route/not-found (slurp (io/resource "404.html")))))
(defn wrap-error-page [handler]
(fn [req]
(try (handler req)
(catch Exception e
{:status 500
:headers {"Content-Type" "text/html"}
:body (slurp (io/resource "500.html"))}))))
(defn -main [& [port]]
(let [port (Integer. (or port (env :port) 5000))
;; TODO: heroku config:add SESSION_SECRET=$RANDOM_16_CHARS
store (cookie/cookie-store {:key (env :session-secret)})]
(jetty/run-jetty (-> #'app
((if (env :production)
wrap-error-page
trace/wrap-stacktrace))
(site {:session {:store store}}))
{:port port :join? false})))
;; For interactive development:
;; (.stop server) ;; <- can't find any documentation for how to use this
;; (def server (-main)) ;; <- or this. When I
un-comment them I get var not found and null pointer exceptions.
(ns clojuregrade.landing
(:require [compojure.core :refer [defroutes GET PUT POST DELETE ANY]]
[compojure.handler :refer [site]]
[compojure.route :as route]
[
clojure.java.io :as io]
[ring.middleware.stacktrace :as trace]
[ring.middleware.session :as session]
[ring.middleware.session.cookie :as cookie]
[ring.adapter.jetty :as jetty]
[hiccup.core :refer :all]
[hiccup.form :refer :all]
[hiccup.element :refer :all]))
;; these functions are used for the process-grades function, which I want to call after having the form-to POST "/" input bound in and passed from the home function.
;; there may be some subtle mistake here that is causing trouble, but I have tested this code pretty thoroughly.
(defn percentify
"adjust raw score to percentile"
[raw percentile]
(* (/ raw 100) percentile))
(defn percentify-vector
"maps a vector of percentile adjustments to a vector of grades"
[weights grades]
(map percentify grades weights))
(defn augment-vectors
"augments all the elements in all of the vectors in a grades list into their corresponding weighted values"
[weights grades]
(mapv (partial percentify-vector weights) grades))
;; I want to take the weights and grades variables from the home function when the user submits the form, and use them in the function below.
;; If I try to use the (read-string weights) (read-string grades) substitues for weights and grades, the function fails in the repl, but without them it works as a stand alone function
;; over lists of vectors. I have a feeling that this is where the key issue lies.
(defn process-grades
"Takes user input from home's form-to function and processes it into the final grades list"
[weights grades]
(->> grades
(map (partial percentify-vector weights))
(mapv #(apply + %))))
;; I updated this.
(defn home [& [weights grades error]]
(html5
[:head
[:title "Home | Clojuregrade"]]
[:body
[:h1 "Welcome to Clojuregrade"]
[:p error]
[:hr]
(form-to [:post "/"]
[:h3 "Enter the weights for each of the grades below. Of course, all of the numbers should add up to 100%. Be sure to include the brackets"
[:br]
(text-area {:cols 30 :placeholder "[40 10 50] <- adds up to 100%"} "weights" weights)]
[:h3 "Enter ALL of the grades for EACH STUDENT in your class.
Make sure that each of the grades is ordered such that the grade corresponds
to its matching weight above."
[:br]
(text-area {:rows 15 :cols 30 :placeholder
"[89 78 63]
[78 91 60]
[87 65 79]
...
(Each grade corresponds to one of the weights above, so order is important. You can copy and paste directly from your excel file but don't forget the brackets!)" } "grades" grades)]
(submit-button "process"))]))
(defn processed [weights grades]
(cond
(empty? weights)
(home weights grades "You forgot to add the weights!")
(empty? grades)
(home weights grades "You forgot to add the grades!")
:else
(do
(html
[:h2 "These are your final grades."]
[:hr]
[:p (process-grades (read-string weights) (read-string grades))]))))
(defroutes app
(GET "/landing" []
{:status 200
:headers {"Content-Type" "text/html"}
:body (home)})
(POST "/" [weights grades] (processed weights grades))
(ANY "*" []
(route/not-found (slurp (io/resource "404.html")))))
(defn wrap-error-page [handler]
(fn [req]
(try (handler req)
(catch Exception e
{:status 500
:headers {"Content-Type" "text/html"}
:body (slurp (io/resource "500.html"))}))))