Fork me on GitHub
#luminus
<
2018-09-23
>
manandearth12:09:20

following the Database Access chapter in Web Development with Clojure, I manage to recreate all the functions in the project. Up until the report generation section: lein new luminus reporting-example +postgres works, the migration works, then recreating the db.core namespace. when I start the repl and call for (mount.core/start #'reporting-example.db.core/*db*) I get an exception: >Caused by java.lang.ClassCastException mount.core.DerefableState cannot be cast to clojure.lang.IFn

manandearth12:09:34

the project copied from the Pragmatic Programmer website works. I understand that it has to do with the actual template that Luminus generates. I tried replacing some of the files with the ones from the finished project but I get into new issues. Is there a simple way to make this work with the current template @yogthos?

yogthos13:09:24

@adamgefen the error means that the db connection state hasn't been started

yogthos13:09:17

mount resolves components using the namespace hierarchy, so if a namespace isn't referenced anywhere the resources defined using defstate will not be started

yogthos13:09:01

ah hang on, just reread the error looks like a different problem

yogthos13:09:18

what does your *db* definition look like?

manandearth13:09:14

I'll rebuild it as I have all my steps documented and I'll post it in a few minutes, as by now I have replaced every bit of code.

manandearth13:09:20

hold on if you can...

yogthos13:09:58

sure thing

manandearth13:09:15

>ClassCastException mount.core.DerefableState cannot be cast to clojure.lang.IFn reporting-example.db.core/eval14346/fn--14347 (core.clj:20)

manandearth13:09:23

that's the original issue

manandearth13:09:07

(conman/bind-connection *db* "sql/queries.sql")

yogthos13:09:26

yeah that error typically occurs when the state isn't started

yogthos13:09:36

where are you running the above command?

manandearth13:09:58

that is in db.core

yogthos13:09:50

can you just paste db.core here

yogthos13:09:09

one problem could be that you have some code that's trying to use the connection at compile time

manandearth13:09:39

>(ns reporting-example.db.core (:require [cheshire.core :refer [generate-string parse-string]] [clj-time.jdbc] [clojure.java.jdbc :as jdbc] [clojure.tools.logging :as log] [conman.core :as conman] [reporting-example.config :refer [env]] [mount.core :refer [defstate]]) (:import org.postgresql.util.PGobject java.sql.Array clojure.lang.IPersistentMap clojure.lang.IPersistentVector [java.sql BatchUpdateException PreparedStatement Date Timestamp])) (defstate ^:dynamic db :start (if-let [jdbc-url (env :database-url)] (conman/connect! {:jdbc-url jdbc-url}) (do (log/warn "database connection URL was not found, please set :database-url in your config, e.g: dev-config.edn") db)) :stop (conman/disconnect! db)) (conman/bind-connection db "sql/queries.sql") (defn to-date [sql-date] (-> sql-date (.getTime) (java.util.Date))) (extend-protocol jdbc/IResultSetReadColumn Array (result-set-read-column [v ] (vec (.getArray v))) Date (result-set-read-column [v ] (to-date v)) Timestamp (result-set-read-column [v ] (to-date v)) PGobject (result-set-read-column [pgobj metadata index] (let [type (.getType pgobj) value (.getValue pgobj)] (case type "json" (parse-string value true) "jsonb" (parse-string value true) "citext" (str value) value)))) (extend-type java.util.Date jdbc/ISQLParameter (set-parameter [v ^PreparedStatement stmt idx] (.setTimestamp stmt idx (Timestamp. (.getTime v))))) (defn to-pg-json [value] (doto (PGobject.) (.setType "jsonb") (.setValue (generate-string value)))) (extend-type clojure.lang.IPersistentVector jdbc/ISQLParameter (set-parameter [v ^java.sql.PreparedStatement stmt ^long idx] (let [conn (.getConnection stmt) meta (.getParameterMetaData stmt) type-name (.getParameterTypeName meta idx)] (if-let [elem-type (when (= (first type-name) \_) (apply str (rest type-name)))] (.setObject stmt idx (.createArrayOf conn elem-type (to-array v))) (.setObject stmt idx (to-pg-json v)))))) (extend-protocol jdbc/ISQLValue IPersistentMap (sql-value [value] (to-pg-json value)) IPersistentVector (sql-value [value] (to-pg-json value)))

yogthos13:09:26

that looks right

yogthos13:09:57

where does the trace for the error point to in the project?

manandearth13:09:24

where do I check that?

yogthos13:09:54

where do you see the exception?

yogthos13:09:16

if you're running the app using lein run that should show the error in the terminal

yogthos13:09:40

is there a stacktrace somewhere?

manandearth13:09:57

I could run it in cider and get a stack

manandearth13:09:03

just a minute

yogthos13:09:11

and what does the function around line 94 look like in core.cljc

yogthos13:09:38

might be easier if you put the project up on github or something so I can take a look locally

manandearth13:09:45

that's the let statement

yogthos13:09:09

are you starting mount states manually there?

manandearth13:09:28

There was nowhere that I defined start and stop, So whatever comes in the box with a new luminus +postgres. plus the protocol extension as in the tutorial.

yogthos13:09:58

but default luminus template doesn't generate the function above, the resources are started in core.clj using the following function:

(defn start-app [args]
  (doseq [component (-> args
                        (parse-opts cli-options)
                        mount/start-with-args
                        :started)]
    (log/info component "started"))
  (.addShutdownHook (Runtime/getRuntime) (Thread. stop-app)))

yogthos13:09:55

the *db* resource relies on the env resource from the config.clj namespace

yogthos13:09:06

and that resource must be started first

manandearth13:09:08

that code comes from mount

yogthos13:09:27

ah ok, so where does the stack trace to in your project

yogthos13:09:09

how are you getting the error

yogthos13:09:20

is it happening when the app starts, or when you run a command in the repl?

manandearth13:09:22

it happens when I run (mount.core/start #'reporting-example.db.core/*db*)

jmayaalv08:09:55

if you want to start the app from the repl, u just need to run (start). The fn is on env/dev/user.clj. mount.core/start is not working probably because the config state is not started and is a dependency for the db state.

manandearth10:09:17

Thanks! works perfectly! @U0J6U23FW

manandearth13:09:05

sorry, I thought I said...

yogthos13:09:07

and has the start-app function from the core namespace been run before then?

yogthos13:09:05

if you're running the app using lein run, and then connecting to nrepl that should cause start-app to run

manandearth13:09:37

brilliant, thanks.

yogthos13:09:43

so what was it? 🙂

manandearth13:09:09

I have no idea... I have to retrace what I did wrong.

manandearth13:09:41

Because I did try with lein run and without.

yogthos13:09:01

my recommendation is to always start the app using lein run, and then connect to the nrepl after

manandearth13:09:25

I guess I had in the back of my mind the idea that it can't work with all the different dependencies to when the book was written

yogthos13:09:26

this way it's closest to the way it would run standalone during deployment

yogthos13:09:55

and it ensures that all the resources are started correctly

manandearth13:09:11

Thanks Dmitri, The book is very helpful.

yogthos13:09:24

thanks, glad to hear it

👍 1