LispCast Web Development in Clojure- Part 2


#1

Hello All, I’m having trouble getting through Part 2 of these 2 videos. I created a new src file for the db functions and added the db functions from the video. I also added the code to the webdev.core ns (where my handlers are), as was done in the video. When I did “lein run 8000,” I got a long stack trace, including this:

Exception in thread "main" org.postgresql.util.PSQLException: ERROR: type "test" does not exist Position: 138

I haven’t been able to figure out the source of the error.

Here’s my model.clj

(ns webdev.items.model
  (:require [clojure.java.jdbc :as db]))

(defn create-table
  "ADD doc string"
  [db]
  (db/execute!
   db
   ["CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\""])
  (db/execute!
   db
   ["CREATE TABLE IF NOT EXISTS items
       (id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
        name TEXT NOT NULL,
        description TEST NOT NULL,
        checked BOOLEAN NOT NULL DEFAULT FALSE,
        date_created TIMESTAMPTZ NOT NULL DEFAULT now())"]))

(defn create-item
  [db name description]
  (:id (first (db/query
               db
               ["INSERT INTO items (name, description)
                 VALUES (?, ?)
                 RETURNING id"
                name
                description]))))

(defn update-item
  [db id checked]
  (= [1] (db/execute!
          db
          ["UPDATE items
            SET checked = ?
            WHERE id = ?"
           checked
           id])))

(defn delete-item [db id]
  (= [1] (db/execute!
          db
          ["DELETE FROM items
            WHERE id = ?"
           id])))

(defn read-items [db]
  (db/query
   db
   ["SELECT id, name, description, checked, date_created
     FROM items
     ORDER BY date_created"]))

Here’s my webdev.clj:

(ns webdev.core
  (:require [webdev.items.model :as items])
  (:require [ring.adapter.jetty :as jetty]
            [ring.middleware.reload :refer [wrap-reload]]
            [ring.middleware.params :refer [wrap-params]]
            [compojure.core :refer [defroutes ANY GET POST PUT DELETE]]
            [compojure.route :refer [not-found]]
            [ring.handler.dump :refer [handle-dump]]))

(def db "jdbc:postgresql://localhost/webdev")

(defn greet
  "This 'handler' function handles GET requests to the home page.  Note: error 'unable to resolve greet in this context' when this greet function followed defroutes app.  So the order of the functions in the code matters."
  [req]
  {:status 200
   :body "Hello, World (now with reload and compojure routing)!"
   :headers {}})

(defn yo
  "This handles GET requests to the path 'yo/[name]'"
  [req]
  (let [name (get-in req [:route-params :name])]
    {:status 200
     :body (str "Yo! " name "!")}))

(def ops
  "Map to turn the ':op' route-param into a clojure function"
  {"+" +
   "-" -
   "*" *
   ":" /})

(defn calc 
  "This handler calculates binary operations with +, -, *, and ':'(division) on integers. It accepts routes like '/calc/10/+/45'."
  [req]
  (let [a (Integer. (get-in req [:route-params :a]))
        b (Integer. (get-in req [:route-params :b]))
        op (get-in req [:route-params :op])
        f (get ops op)]
    (if f
      {:status 200
       :body (str (f a b))
       :headers {}}
      {:status 400
       :body (str "Unknown operator: " op)
       :headings {}})))

(defn about
  "This 'handler' function handles GET requests to the 'about' past"
  [req]
  {:status 200
   :body "This app is a template for future apps, created following lispcast Clojure Web Dev 2-part tutorial"
   :headers {}})

(defn goodbye
  "This 'handler' function handles GET requests to the 'goodbye' path"
  [req]
  {:status 200
   :body "Goodbye, cruel world!"
   :headers {}})

(defroutes routes
  "Uses compojure to route requests to appropriate handler depending on request type (ie GET or not-found, in this case) and path.  Notice: defroutes, unlike defn, is not immediately followed by an argument in brackets."
  (GET "/" [] greet)
  (GET "/yo/:name" [] yo)
  (GET "/calc/:a/:op/:b" [] calc)
  (GET "/goodbye" [] goodbye)
  (GET "/about" [] about)
  (GET "/request" [] handle-dump)
  (not-found "Page not found."))

(def app
  (wrap-params
   routes))

(defn -main
  "This handler creates a db & starts the jetty adapter to listen to a port, pass HTTP requests to my handlers, and send their responses back through the port."
  [port]
  (items/create-table db)
  (jetty/run-jetty app
                   {:port (Integer. port)}))

(defn -dev-main
  "Reloads server when changes are made to code, including -main adapter"
  [port]
  (items/create-table db)
  (jetty/run-jetty (wrap-reload #'app) {:port (Integer. port)}))

Any thoughts on my stated issue or anything else that sticks out, is much appreciated. Thank you!


#2

Hello nzey!

I searched for “test” and discovered a possible typo: should the following say TEXT, not TEST?

description TEST NOT NULL,


#3

Hi @nzey,

Sorry you’re having trouble! I think I need to give the source just in case something like this happens. It’s on my todo list now. Thanks for sharing!

I think @tjg is right. This is a problem with the SQL (we can tell because of the PSQLException). It is saying that you asked for a column type of “test” (or TEST since sql types are not case sensitive). But that type does not exist. You meant TEXT, which would make sense for the text of the description.

Thanks for sticking through this. I know you’re new to programming (you told me in an email) and this is one of those unfortunate problems in programming that a small typo like that can cause huge problems and very often take a long time to figure out. I will speak for all programmers and say that this happens to all of us all the time. It’s very common that I’m stumped because of a typo.

It’s frustrating, but there are some upsides to this problem. One is that there’s lots of people willing to help! We’ve all been there and sometimes it’s easy for someone else to spot the typo. Another is that, with time, you will be able to find your typos faster by chasing the clues (the exception type and text, etc). And finally, you do make fewer typos over time, so the nice period between them, where you’re very productive, gets longer.

Thanks again for sharing! I’m sure this will help others. It’s great to see you learning.

Rock on!
Eric


#4

You’re right, jtg and Eric. Thank you! I made that change. In this case, it was particularly hard for me tell what was a typo because I wasn’t clear on the purpose of all the things in the database functions. As I get more experience, I think it will also get easier to spot typos.

I still got another error -
java.io.FileNotFoundException: Could not locate webdev/item/model__init.class or webdev/item/model.clj on classpath.

When I renamed the namespace to webdev.model, it worked. Maybe I needed to have a directory called item, in which model.clj was saved, to be able to name the namespace webdev.item.model? But the video didn’t mention why it used “item” in the ns or why an “item” directory should be created (it just mentioned having a model.clj, handler.clj, and view.clj). I need to back up and read again about namespaces, but in the meantime, could you explain the namespace used in the video? (thanks for your time!)


#5

Hi @nzey,

Congrats on continuing through these issues. These are very common and it’s great that you are persevering.

Yeah, namespace naming can be confusing. I explain how namespaces are named, oddly enough, in the Intro to clojure.test course. It’s a short intro to the topic of how to name namespaces to avoid the gotchas. It’s part of the Online Mentoring program, so if you like, check it out.

Just a quick summary: namespaces are named like webdev.item.model. The namespace is divided into parts by .s (periods). The last part is the filename (with .clj extension). The other parts are the directory path starting with src/. So webdev.item.model corresponds to the file src/webdev/item/model.clj. You should make sure the namespace declared at the top of the file ((ns webdev.item.model)) corresponds to the file path on your hard drive (src/webdev/item/model.clj). That’s so that Clojure knows where to look when it’s including it.

That FileNotFoundException indicated that Clojure could not find it where it expected it to be. I usually see that error when I :require a namespace with a typo or it’s not there for some other reason.

Thanks again for sharing and I’m more than happy to help you through future issues.

Rock on!
Eric