Fork me on GitHub
#beginners
<
2021-09-24
>
Frederico Benevides00:09:01

Hi everyone! I'm trying to understand the relation between keys and list. For example. This code return the keys from a map (keys {:fname "frederico"}) ; it will return :fname If I change the code to load it from a list, it will not work as expected. (keys (:fname "frederico")) Now if I send a list using quote to a function, I'm able to load the keys, even the args is a list. I even printed to validate it. (defn fullname [args] (println args) (let [{:keys [fname lname]} args] (println "My fullname is" fname lname))) (fullname '(:fname "Frederico" :lname "Benevides")) So is there some specific reason why I'm able to use keys in this way ?

randomm char00:09:37

it needs to be (keys {:fname "frederico"}) keys needs a hashmap?

Bob B01:09:09

I think the primary reason is because it coincides with how kw args work

☝️ 1
Frederico Benevides02:09:27

Thank you guys. I found a explanation from The Joy of Clojure. "Using a map to destructure a list of arguments causes the list to first be poured into a map collection before then being destructured as usual"

randomm char02:09:32

Just knowing the right keyword is all yeh need 😛 now got more research to do but first gotta do the sleep thing

noisesmith18:09:07

> If I change the code to load it from a list, it will not work as expected. > (keys (:fname "frederico")) in this code, (:fname "fredirico") is evaluated as a call to lookup :fname, which always returns nil when the arg is not keyed (strings are not keyed)

noisesmith18:09:32

lists are treated as calls unless somehow escaped or quoted

noisesmith18:09:08

in context it looks like you actually had a seq (from args) (:fname (list :fname "frederico"))

Clojure Newbie03:09:04

Hello all. I have a quick question about macros. Is there any way to map over a list of parameters to the macro and add more functions before evaluation, effectively changing what the function does? For example, I envision a macro that maps something like (testing (+ 1 2) (+ 3 4)) to (do (cons 3 ('+ 1 2)) (cons 3 ('+ 3 4))? I have tried numerous variations(using 'body, body, @body) passing in [& body] to the macro, but when I try to map through it and add the symbols, I get numerous errors. I have also tried to google the answer to this question and read through clojure for the brave and true section on macros numerous times.

lsenjov03:09:14

First thing that grabs me is the (cons 3 ('+ 1 2)) which is going to throw an error. You’re wanting (cons 3 '(+ 1 2)).

(defmacro asdf
  [body]
  `(concat ~body '(5)))
(macroexpand (asdf '(+ 3 4)))
;=> (+ 3 4 5)

lsenjov03:09:41

I’m using concat instead of cons, because cons will put it on the front, and you want this on the end

lsenjov03:09:22

The backtick is basically like ' except it namespaces the symbols, which is very useful for not getting namespace issues later

lsenjov03:09:46

Finally, you need to ~body to resolve it within the quote

Clojure Newbie03:09:49

Addition was a very confusing example to use. My bad. I just want to be able to essentially add things to the parameters pre evaluation

Clojure Newbie03:09:55

is it not true that, in that example, when you do ~body, body would get evaluated before you concat

lsenjov03:09:05

Final bit of explaination: at the time of evaluation, body is equal to '(+ 3 4), then you modify it as you would any clojure data structure

lsenjov03:09:34

So when you evaluate body within the quoted block, it resolves to '(+ 3 4)

Clojure Newbie03:09:49

It does not resolve to 7.

👍 1
3
lsenjov03:09:38

It’s been passed a data structure, it won’t do anything with that unless you call eval somewhere

Clojure Newbie03:09:57

That means my error was elsewhere and I was totally off. Thus, I should be able to map through(if I had &body) and add something to each.

👍 1
lsenjov03:09:11

Macroexpand is your friend

Clojure Newbie03:09:41

Yeah, I tried using it but still got confused by the output lol. I'm gonna take it slow. Thanks for the help!

lsenjov03:09:10

No problems, good luck!

Clojure Newbie03:09:33

I just realized, in order to it this way, you have to pass the parameters in as backticked right? There is no way to do it without that?

lsenjov03:09:49

Nah, only for macroexpand

lsenjov03:09:59

If you use this generally, it’ll expand and then evaluate

lsenjov03:09:43

From my example, evaluating (asdf (+ 3 4)) will result in 12

Clojure Newbie03:09:05

Currently I have (defmacro testing [& body] (map (fn [x#] (println x#)) ~body))`

Clojure Newbie03:09:10

and I get an exception

lsenjov03:09:57

Why the backtick at the start?

Clojure Newbie03:09:00

Oh wait I think I see what I did wrong.

Clojure Newbie03:09:24

Don't you need it to return a list that evaluates the map?

lsenjov03:09:48

body is already a list

lsenjov03:09:15

You can do (defmacro qwer [body] body) for it to do nothing

lsenjov03:09:35

It gets a data structure in, and returns that data structure

lsenjov03:09:11

If you’re returning (map (fn …))`, then it’s going to literally return (map (fn …)) before evaluating it

lsenjov03:09:08

The other thing to be aware of is that it’s going to return a list

lsenjov03:09:27

And that first item is going to be a println that returns nil

lsenjov03:09:42

So your return body will evaluate to (nil …) which is going to throw an error

Clojure Newbie03:09:51

Yes it did haha

lsenjov03:09:22

Again, macroexpand

Clojure Newbie03:09:12

Yes, I will have to get used to using that often

👍 1
lsenjov03:09:29

Also, macros are complicated, so where possible tend away from using them

lsenjov03:09:11

This is probably a decent spot for it, but in general most of the time you need a macro, a function will do

lsenjov03:09:21

Just a warning before you start using macros everywhere and hate yourself for it

Clojure Newbie03:09:37

Yeah, i've read about that a lot before

Clojure Newbie03:09:48

I feel like everyone goes through the phase of wanting to use them everywhere lol

lsenjov04:09:40

Maybe, we’ll see how it goes when you grok macros properly 😛

noisesmith19:09:36

something that worked for my (YMMV) was to write macros without using ` - just using list / cons / concat etc. until I really understood what they were doing

noisesmith19:09:33

backtick isn't "the macro syntax", it's a reader macro that splices values into list literals - something that's super useful when writing macros but also it can obfuscate what you are really doing

John Bradens18:09:05

I've been following the book Web Development with Clojure 3rd edition and trying to deploy the app to Heroku. I got the app to show up on Heroku, but I can't figure out how to make the database work. Meaning I just see "loading messages..." on the home page and cannot log in or anything. I added the postgres plug-in following the steps from the book. Any advice? What changes do I need to make to my config file? I am also trying to run `heroku run lein run migrate` but having issues. It says it can't find a non empty config file, but I do have a dev-config.edn, probably with outdated/incorrect info in it.

practicalli-johnny18:09:03

There is a #heroku channel If you can share the code from your project, I can take a look (otherwise I would only be guessing). I dont have the book, but am familar with Heroku.

John Bradens19:09:27

Thanks @U05254DQM I really appreciate it. I'm definitely having trouble understanding what to do with the config.edn file. From the book I had dev-config.edn but then it says to have a prod-config.edn. I know I probably need to change the database-url to be heroku credentials instead of my local ones, but I'm not sure how.

practicalli-johnny19:09:49

It will probably be me answering in the heroku channel, so may as well continue here

John Bradens19:09:54

Oh ok. Thanks!

practicalli-johnny19:09:32

I usually just have the one config.edn file and place it in /resources , using juxt/aero profiles to define values for different environments If the book is suggesting different files, I assume those config.edn files are included via paths of leiningen profiles

practicalli-johnny19:09:14

The config.edn file doest seem to be on the class path, so wont be available to load (unless I missed something in the profies.clj file

John Bradens19:09:14

Thanks I definitely forgot to add it to the class path. How do I do that? In project.clj I have :project/dev {:jvm-opts ["-Dconf=dev-config.edn" ] would I replace dev-config.edn with just config.edn? I don't think I have a profiles.clj file should I add one?

practicalli-johnny19:09:43

Hmm, it seems the config is being loaded by mount using the kewl-app.config namespace, although I am not following that bit yet

John Bradens19:09:16

Ok interesting. It's based on a luminus template and I am not sure how all the details work unfortunately

practicalli-johnny19:09:51

Yes, there is a lot of learning in the luminus template. Looking at how it builds the uberjar, which is the thing that Heroku will ultimately run (via the Procfile), it sets a number of paths for resource files https://github.com/johnbradens/kewl-app-4/blob/main/project.clj#L68-L75

practicalli-johnny19:09:48

so the Heroku database connection should be in the env/prod/resources/config.edn file I believe

John Bradens19:09:32

Ohhhhh I see now. I found the file there!

John Bradens19:09:29

It just says {:prod true :port 3000} I'm assuming I must make some changes? And I'm curious why heroku run lein run migrate said that it couldn't find a nonempty config file? :thinking_face:

practicalli-johnny19:09:39

Heroku has a DATABASE_URL environment variable that can be used to pull in the address of the database, eg. via System/getenv "DATABASE_URL" https://practical.li/clojure-web-services/projects/leiningen/todo-app/postgres/environment-variables.html

practicalli-johnny19:09:03

Or you may need JDBC_DATABASE_URL` instead, it depends on the form of the connection URL required, this is also automatically created by Heroku when adding a database

John Bradens19:09:48

Or is it ok to just say database_url: System/getenv "DATABASE_URL" and then it automatically fills in the database_url? Or am I supposed to substitute mine in there?

John Bradens19:09:09

Luckily there's nothing on there yet 😉

practicalli-johnny19:09:01

Using the environment variables is the recommeded approach, as if you reset the database or remove / add a new one then the environment variable will be automatically updated and no code changes are required

John Bradens19:09:05

Ok cool. so then I just want to update my config.edn file to say {:prod true :port 3000 :database_url System/getenv "DATABASE_URL"} ?

practicalli-johnny19:09:11

mostly. You should also use PORT environment variable for port

John Bradens19:09:01

{:prod true
 :port System/getenv "PORT"
 :database-url System/getenv "DATABASE_URL"}

John Bradens19:09:28

Does this look ok or do I add parenthesis like :database-url (System/getenv "DATABASE_URL")}

practicalli-johnny19:09:32

Well, I've just noticed this line in the -main file, it seems that its looking for a key called :database-url https://github.com/johnbradens/kewl-app-4/blob/main/src/clj/kewl_app/core.clj#L63

practicalli-johnny19:09:19

so I beleive your line above should work with that - so long as the connection string is in the right form 🙂

John Bradens19:09:43

Ok great I'll try it

practicalli-johnny19:09:55

{:prod true
 :port (System/getenv "PORT")
 :database-url (System/getenv "DATABASE_URL")}

John Bradens19:09:16

Great thanks for your help! Ok so I just made the changes to the config.edn file, then did git add ., git commit -m "fixing things", and then git push heroku main.

John Bradens19:09:10

How will I know if the connection string is in the right from? I guess based on whether it connects or not? Thanks again for all your help, I really appreciate it.

practicalli-johnny19:09:19

If it works, it was the right form, otherwise replace DATABASE_URL with JDBC_DATABASE_URL

John Bradens19:09:33

After I compile, generally would I have to do heroku run lein run migrate or can I just open the app and see it work?

John Bradens19:09:56

Ok it didn't work when I opened the app just now after it recompiled. I'll try JDBC_DATABASE_URL now. Thanks!

practicalli-johnny19:09:45

If it complains about the port when starting the application server, then try set it in the Procfile instead, by adding --port $PORT to the end of the existing command

John Bradens19:09:00

Ok I didn't see any complaints about the port so I think that part is ok

practicalli-johnny19:09:05

I would generally recommend running the migrations before doing git push heroku master to deploy the application, to ensure the deployment doesnt time out running migrations (it probably wont, unless big changes are made)

John Bradens19:09:45

Ok good to know! I will try that next time.

practicalli-johnny19:09:24

heroku run lein run migrate spins up a new container that just runs the migrations, rather than the application server I beleive

John Bradens19:09:52

Ok, so if I don't run that line, the database should still work?

practicalli-johnny19:09:44

The Heroku database should be running regarless of if your application is up. So you can do migrations at any time as a separate process

practicalli-johnny19:09:32

Deploying with git push heroku master will build the application from source code, making an uberjar and then running the application as defined in the Procfile.

John Bradens20:09:41

So I changed to JDBC_DATABASE_URL and just recompiled. I still see the app with "Loading messages..." and not able to login or make an account. Is this because I didn't do heroku run lein run migrate or is there something else I have an error with?

practicalli-johnny20:09:17

FYI. I find the Heroku CLI very useful to see the logs as its deploying the app https://devcenter.heroku.com/articles/heroku-cli

practicalli-johnny20:09:42

If the errors are in the web UI, then I assume the front-end is just not being built. Heroku will just do whats defined in the uberjar task, so maybe something else needs to be done to build the web ui.

John Bradens20:09:12

Ohh ok I see. I will have to tinker with it some more

John Bradens20:09:15

When I run heroku run lein run migrate I still get this error :thinking_face:

Syntax error compiling at (/tmp/form-init112362889056766708.clj:1:72). could not find a non empty configuration file to load. looked in the classpath (as a "resource") and on a file system via "conf" system property

practicalli-johnny20:09:00

Migrations seems to be the first thing to run when deploying the application too, so if the migrations are failing, then nothing is going to work properly. Have you tried migrations with a local postgresql database?

John Bradens20:09:12

Would that be with heroku as well or all on my local computer? Before I deployed to Heroku, I was able to have everything working locally in my local database and using the app in localhost

John Bradens20:09:47

The luminus template has a command (migrate) to run in the lein repl, so I was doing that before.

John Bradens20:09:31

I'll change the database url to my local one and try lein run migrate and see what happens.

practicalli-johnny20:09:36

Yeah, the luminus template probably takes a few days study to understand it well, especially if its the first web app you've created. I found this code in the luminus-migrations.core namespace that suggests its looking for the Heroku form of database-url , but that is an assumption and it might just use the same name

(defn- parse-url
  ([opts] (parse-url opts identity))
  ([{:keys [database-url] :as opts} transformation]
   (if database-url
     (-> opts
         (dissoc :database-url)
         (assoc-in [:db :connection-uri]
                   (to-jdbc-uri (transformation database-url))))
     opts)))

John Bradens20:09:50

Ok interesting. So I might try :database-url "insert heroku string with my username and password info here"

practicalli-johnny20:09:18

The error you get when running `heroku run lein run migrate` suggests the config.edn is not being loaded. Did you deploy the new version of the application after making the change, i.e. git push heroku master ?

John Bradens20:09:14

Yes every time I make a change, I do git add ., git commit, and then git push heroku main

John Bradens20:09:31

I'll run it again now just to be sure

practicalli-johnny20:09:52

I am fairly sure the config.edn should use the DATABASE_URL environment variable. luminus-migrations uses migratus which takes the postgres: form of connection, so it should just work If you added the --port $PORT to the Procfile, I assume the :port 3000 is not needed in the config.edn file.

John Bradens20:09:51

Ok I made those changes. I added --port $PORT at the end of the statement in my procfile, and commented it out in my env/prod/resources/config.edn file. Now my config file has just the following and I'm gonna try running it again...

{:prod true
 :database-url (System/getenv "JDBC_DATABASE_URL")}

Colin P. Hill20:09:43

Browsing Om Next docs, I encountered this expression:

#js {:onClick
               (fn [e]
                 (swap! app-state update-in [:count] inc))}
I can’t seem to find documentation on that reader macro. Anyone know what it does?

phronmophobic20:09:25

#js will create create a javascript object or javascript array

Colin P. Hill20:09:25

Ah, so it’s just an ordinary flat mutable object instead of a persistent map?

👍 2
phronmophobic20:09:51

It was pretty hard to find in the docs. I couldn't find it in any of the guides.