Fork me on GitHub
#beginners
<
2016-12-21
>
ssansovich00:12:37

I’m not sure if this is the right place or format, but I’m really struggling with getting file uploads to work through compojure/clj-http. I’ve posted an SO question with more details: http://stackoverflow.com/questions/41253088/422-unprocessable-entity-response-when-posting-file-upload-in-clojure

ichise.masashi00:12:32

I have a question. A function has become easily longer like "namespace + function". How can I define another shorten name?

user=> (def d clojure.repl/doc)

CompilerException java.lang.RuntimeException: Can't take value of a macro: #'clojure.repl/doc, compiling:(/private/var/folders/c8/ynqzx6vs51nfv5jzxb11tr600000gn/T/form-init7130854364890860285.clj:1:1) 

noisesmith00:12:53

ichise.masashi: you can use require with :as or rename

noisesmith00:12:12

or with clojure.repl, even just use (though use is not recommended in general)

noisesmith00:12:33

(use 'clojure.repl) (doc +)

ichise.masashi00:12:09

thanks. I konw about use. the way is dangerous.

ichise.masashi00:12:29

and require ... :as shorten only namespace.

noisesmith00:12:29

phoenix.core=> (require '[clojure.repl :as r :refer [doc] :rename {doc d}])
nil
phoenix.core=> (d +)
-------------------------
clojure.core/+
([] [x] [x y] [x y & more])
  Returns the sum of nums. (+) returns 0. Does not auto-promote
  longs, will throw on overflow. See also: +'
nil

noisesmith00:12:41

so that lets you refer and rename together

noisesmith00:12:49

I couldn't get it to rename without referring though

ichise.masashi00:12:46

ohhh, thank you very much. I had wanted just like this.

roelofw07:12:27

Is it possible to display something like 1 2 3 4 ...... 398 399 400 as pagination and change that when we are for example on page 3 where 2 3 4 5 398 399 400 must be displayed

agile_geek08:12:31

@roelofw not sure what your trying to 'paginate' but you could use drop and take to very simply select a range of items from a collection. For example:

(let [numbers (range 1 401)]
    (take 10 (drop 10 numbers)))

roelof12:12:44

@agile_geek I m trying to find a way I can display more paintings.

roelof12:12:16

I have now displayed 10 but if I want I can display some 4700 with this api

agile_geek12:12:19

@roelofw so one way of doing this would be to store the id's in the session and then use take and drop to retrieve the appropriate 'page' of id's (you would need to store the index of the first id on the current page and the page size (number of paintings per page) as well.

roelof12:12:39

@agile_geek so store all the id's in a sesssion ?

agile_geek12:12:32

That's one way.

agile_geek12:12:40

Alternatively as the id's don't change often you could store them in an atom when the server starts up (make the get id's api call just once at start up) and then just store the index of the first id on current page and the page size for each user in their session

roelof12:12:44

Then I have to rewrite my read-numbers function

agile_geek12:12:08

You can call that function and store the results

roelof12:12:48

oke, make a new function that runs 470 times to read all the ids

agile_geek12:12:05

Ah I see what you mean

roelof12:12:09

maybe that one can also be in parallel.

roelof12:12:31

Otherwise it will take a long time before a user sees the first page

agile_geek12:12:35

Is there an API call that can fetch all painting id's in one call?

agile_geek12:12:16

You could rewrite your fn to make that call and store the results.

agile_geek12:12:54

If you do that at server startup and store in an atom it would only delay the start of your server but it wouldn't have to call for every user

roelof12:12:25

no, I can maxium call 100 paintings at once

agile_geek12:12:06

Ah OK. Still you could make the calls at server startup as the ids won't change much over time.

roelof12:12:26

nope, I think the ids never change

agile_geek12:12:03

It would mean any new paintings wouldn't appear until you restarted the server (or you added a timer to refresh the cache of ids [the atom] every few hours)

roelof12:12:08

next thing to find out . How can I do something at server startup on luminus

agile_geek12:12:36

I'd have to look it up. Check the documentation

agile_geek12:12:41

never used liminus

roelof12:12:28

I think I have to do something with ring

roelof12:12:40

luminus uses ring and compojure

roelof12:12:19

if I read here . I can use init to read it

roelof12:12:59

@agile_geek do you sometimes fork my project to take a look at it

agile_geek12:12:35

You could just add the call inside the Def of the atom then it would run on load of the app

agile_geek12:12:27

You might want to use defonce rather than def if you are using reload middleware otherwise you'll call the API everytime you reload your code in development

roelof12:12:45

and that will be a lot during testing ideas

roelof12:12:07

never knew you could add a function to a atom

roelof12:12:25

I think you mean something like this: (def apple-price (atom nil))

agile_geek12:12:11

You're not adding a fn to an atom (although you could) you're constructing the atom from the results of a fn call

agile_geek12:12:42

So instead of nil call a fn to return the vector of ids

agile_geek12:12:54

(def apple-price (atom (get-apple-price)))

agile_geek12:12:55

Or probably better (defonce apple-price (atom (get-apple-price)))

roelof12:12:19

oke, and that one has to be repeated some 470 time where the number repeated is the page number

agile_geek13:12:06

Well you'll need to write one fn that makes all calls necessary and returns one vector of ids

roelof13:12:15

that I could do with (defonce apple-price( atom ( for x [ 1 .. 470] (get-apple-price x ))))

roelof13:12:28

out of my head

sveri13:12:15

@roelof If you were sitting next to me and I would be incorporating you I would tell you to start way earlier. Just define an atom that you initialize on application startup with a static value. Make that work first.

roelof13:12:25

I trying now with google and reading to find out how I can initialize a function or a atom on startup

roelof13:12:41

IM using luminus at the moment

agile_geek13:12:02

Don't use for either as it's lazy evaluated. See your experiment the other day

roelof13:12:42

oke, I tried to find a way to make a sort of loop where I can handle the index

roelof13:12:08

but first things first , find out how I can initialize a atom on startup of a server

agile_geek13:12:14

If you are passing in a number to the api calling function (like in your for example) you can user mapv over a list of numbers:

(mapv get-apple-price (range 1 471))

agile_geek13:12:33

Where get-apple-price takes a number as an argument

roelof13:12:01

oke, the approach we did on the data and image reading

agile_geek13:12:25

Note: the range is (range 1 471) as the end index is exclusive that will produce (1 2 3 4....470)

agile_geek13:12:58

Presumably you are going to have to write a fn to call the api that fetches id's based on some parameter to the api? I am guessing that's not a number?

roelof13:12:23

nope, it something like this XX-X-NN where X is a character of [a-z] and n is a number between 1 and 99

agile_geek13:12:30

I mean't that you need a vector of all the painting id's. If you can only fect so many ids in each call how will you make the second call to get the next lot of ids? At the moment I think your query getting the ids is fetching all the ids for a specific painting collection?

(defn home-page []
  (let [url ""
        options {:as :json :query-params {:key (env :key) :format "json" :type "schilderij" :toppieces "True"}}]
        (layout/render
          "home.html" {:paintings (-> (client/get url options)
                                      api/read-numbers
                                      api/do-both-in-parallel)})))

agile_geek13:12:01

I am not sure how you would change that url to fethc the next lot of ids?

agile_geek13:12:04

Do you need a vector of collection types that you mapv over?

roelof13:12:08

I have to include this in my options :

p   The result page 

roelof13:12:11

and this one :

ps  The number of results per page 

roelof13:12:14

so it will be something like this :

 options {:as :json :query-params {:key (env :key) :format "json" :type "schilderij" :toppieces "True"  :p  p  :ps 10  }}]  

roelof13:12:25

where p is the page we want to see

roelof13:12:50

so p will be between 1 and 470 if we want 10 pieces a page

roelof13:12:23

@agile_geek that part is clear to you

agile_geek13:12:59

The API supports those P and PS arguments?

roelof13:12:28

yep, I found those on the page where the api is explained

roelof13:12:14

I could do a lot more on that api call if I want

roelof13:12:37

the only thing is that ps must be between 1 and 100

roelof13:12:44

I even can make two calls where one will be a Dutch text and the other a English text so a user could choose the language

roelof13:12:16

so (mapv read-numbers (range 1 471))

roelof13:12:21

calls this function :

(defn read-numbers

  "Reads the ids of the paintings"

  [response]

  (->> (:body response)

       :artObjects

       (map :objectNumber)))   

roelof13:12:58

which needs this one :

(defn home-page []
  (let [url ""
        options {:as :json :query-params {:key (env :key) :format "json" :type "schilderij" :toppieces "True" }}]
    (layout/render
      "home.html" {:paintings (-> (client/get url options)
                                  api/read-numbers
                                  api/fetch-paintings-and-images-front-page)})))  

roelof13:12:36

so I have to rewrite I think home-page so the atom is used

agile_geek13:12:52

You need to mapv a fn that makes an API call. read-numbers just gets data from the response from the API call

roelof13:12:48

So I think before `client/get I have to make the call

agile_geek13:12:57

TBH I'm not sure. I haven't got time to investigate the API.

roelof13:12:26

the api to we make the call ?

roelof13:12:27

that one is happy when we provide the parameters that it need

roelof14:12:10

at second thought it not the right place. Then it's get called everytime we visit a page

roelof14:12:38

I think I have made a monster where adding more features is not easy to do

agile_geek14:12:08

Yeah I think you need to make your api calls in a function that you call from the atom def

agile_geek14:12:25

I disagree about adding features. You just need to refactor some of your code into other functions.

agile_geek14:12:26

The issues you are experiencing are not to do with Clojure but are about the design of the app and an understanding of the api's you are calling. This can happen in any language.

roelof14:12:17

I made this one earlier in rails but then I had the luck that there were plugins that could do a lot of work for me

roelof14:12:39

and the design in rails is already made

agile_geek14:12:05

Ah but if you wanted to do something the plugins couldn't you would be in a worse position. That's why a lot of experienced developers prefer composing functions in the way clojure does. It's a steeper learning curve but it pays off in the long run

roelof14:12:29

I think I understand the api that im calling but have a lack of understanding how to use it in a way I can use so I can add things later

roelof14:12:49

in rails there are plugins for almost everything

roelof14:12:09

if you have a problem, a normal answer is use that plugin

roelof14:12:58

I have to go for some time. I have to do some groceries

roelof14:12:27

I will think about how I can refractor this so I can make the pagination work

roelof14:12:34

thanks for thinking with me

sveri14:12:52

@roloef There is a difference between programming a system and plugging things together. You can get work done with both. So I am on no side here. Its just that, both things also serve different goals / a different audience with different drawbacks. If you have something that works, why write it in a new language?

agile_geek14:12:55

True. Although you are wroking with different constraints in both cases.

roelof14:12:14

to learn a new language

roelof14:12:31

and I found the solution I made in ruby not well

roelof14:12:48

I download there all the data first and put it into a database

roelof14:12:04

That costs me 4 - 5 hours

roelof14:12:29

I wondered if there is a better solution and I think I found it

roelof14:12:15

With the solution now it costs me 1 - 2 seconds to display a page

roelof14:12:31

and I do not have to use a database

roelof14:12:45

and I can make my route flexible

roelof14:12:25

I can now use /detail/sk-c-5 where on rails I had to use /paintings/1

roelof14:12:41

I find the clojure url more expressive

sveri14:12:55

@roelof Hehe, no need to convince me, I was just curious

roelof14:12:02

@agile_geek maybe a solution to find out if I can make it work that the call to the api will be on startup

roelof14:12:31

then p could be parameters on the read-numbers function

roelof15:12:27

@sveri no problem. I only try to give you a answer to your question why make something on clojure where I have made it with rails

roelof15:12:50

I think I learn more about design of a app with clojure

roelof15:12:31

@agile_geek could that idea work ?

agile_geek15:12:24

@roelofw calling the api on startup? Yes. that's what I was suggesting. Call it to retrieve the ids and store them in an atom that you can page over. However if the api supports pages you may not even need to do that. Simply store the current page number in the session and extract that from the session for every request and pass it to the api to show a page full of id's.

roelof15:12:00

oke, then I have to find how to work with sessions

roelof15:12:12

when I use sessions is my app still stateless ?

poooogles15:12:22

depends where you're storing them

roelofw16:12:50

I assume that sessions are always stored local so not on aan server

agile_geek16:12:09

I mentioned this a few days back. By default sessions in ring are stored in an in memory store https://github.com/ring-clojure/ring/wiki/Sessions

agile_geek16:12:59

so there is some state (data saved per session) but you don't have to worry about managing it.

agile_geek16:12:16

If you use the encrypted cookie version of ring sessions you end up passing the session data as an encrypted cookie in the request/response so your server would actually be stateless. I would just use the in memory default for now.

roelofw16:12:11

Oké So if i understand you right, store the current page number in a session and use that for aan request to the api

dpsutton16:12:06

@roelof @roelofw are yall the same person?

roelofw16:12:00

Im from the netwerkadapter and

roelofw16:12:20

Sorry. I workshop mobile

roelofw16:12:35

It can be, earlier ik use the name roelof

wilcov16:12:59

I think they are seeing as both usernames use 'oke' instead of the normal 'okay'. It's a clear sign imo

roelofw17:12:32

Oké is dutch for okay

wilcov17:12:11

Fair enough

roelof17:12:33

im back to my normal computer

roelof17:12:23

the idea is to store the current page in a session and use that for displaying a few numbers before and after it and use that for request to a api, Do I understand the idea well ?

agile_geek17:12:53

Basically- yes

roelof17:12:52

oke, and then let de displat change the session so that a new page is shown

roelof17:12:05

I think I can make that idea work

roelof17:12:18

Thanks all for thinking with me

sveri17:12:12

@roelof I would like to stress that again. Take your plan and split it down into very small tasks that build upon each other. Start as easy as possible and try to write tests for your functions.

roelof17:12:44

I know that , I can now make a plan to make this work in small steps

roelof17:12:00

and of course I try to make tests

roelof17:12:31

The only things that do not have test in my app are the tests where everything is read and the routes

roelof17:12:39

and the templates.

roelof17:12:58

I still have to learn how to make test for these

agile_geek18:12:37

@roelofw actually I've been a bit stupid. You can do this without sessions. If you pass page number as a query parameter from a link on your home page. i.e. you have a link on home page for next page that passes the next page number (i.e. 2 if you're on page 1) in the query params of the href i.e. /?page-num=2 You can then change your home-page fn and the route for home-page to get the page-num from the query string and pass it to the query for the api call. You would need to return some new data in the rendered page i.e. next page number and previous page number. You would also need to add {% if %} excpressions to work out if you want to render the 'next page' and 'prev page' links depending on their values i.e. if next page number is < 470 render next page link otherwise don't as you have the last page.

roelof19:12:25

oke, I can do that also. Then I could make a seq a few numbers smaller then the current one and some more

roelof19:12:02

I have then to check two things. The numbers smaller cannot be smaller then 1 and not bigger then 470

roelof19:12:48

I will play wit it the next few days

josh_tackett19:12:13

what is the best way to transform a vector into arguments?

josh_tackett19:12:40

I have a vector of strings that I want to use as arguments to a format function (format "%s \n %s, %s" ["a" "b" “c”])

josh_tackett19:12:47

like this but I need to remove the vector

noisesmith20:12:08

roelof reduce takes a two argument function

roelof20:12:54

Stupid of me

roelof20:12:34

@josh_tackett you mean something like the last example ?

josh_tackett20:12:15

(apply format format-string data)

josh_tackett20:12:24

nope like this ^^^

josh_tackett20:12:48

@roelof the join doesn’t add the formatting string

roelof20:12:34

@josh_tackett oke, which format do you want it to be then

josh_tackett20:12:55

@roelof I already figured it out

josh_tackett20:12:01

this was the answer: (apply format "%s \n %s, %s" ["a" "b" “c”])

roelof20:12:16

@josh_tackett , when I try your solution I see error messages

noisesmith20:12:46

@roelof that solution actually works though

roelof20:12:15

wierd that clojure bot and on my repl I see error messages

noisesmith20:12:26

@roelof also, you can use your direct messages window to try out plugins without spamming the channel if you don't know what the result will be

noisesmith20:12:47

phoenix.core=> (apply format "%s \n %s, %s" ["a" "b" "c"])
"a \n b, c"

roelof20:12:58

I see this on my repl :

(apply format "%s \n %s, %s" ["a" "b" "c"])
CompilerException java.lang.RuntimeException: Unable to resolve symbol: ⁠⁠⁠⁠ in this context, compiling:(C:\Users\rwobb\AppData\Local\Temp\form-init5435960750035625814.clj:1:984) 
=> "a \nb, c"  

noisesmith20:12:30

looks like you copy pasted an nbsp at the beginning of the line before the code - the code ran

noisesmith20:12:56

(and had the right result even)

roelof20:12:17

I think so , when I copy/paste your solution it works fine

roelof20:12:43

sorry then for stating that the code do not work

jalvz21:12:28

Hello guys, i have troubles understanding this reduce output:

(reduce (fn [acc x] (when (odd? x) (assoc acc x x))) {} [1 2 3])

{3 3}
I was expecting {1 1, 3 3} - what am i missing?

donaldball21:12:09

Try using reductions instead of reduce to see the intermediate output

sveri21:12:36

@jalvz what does when return if the condition is not met 😉

jalvz21:12:48

ah, now i get! thanks, didnt know about reductions 👍

donaldball21:12:41

I quite like the cond-> form for reducing fns to avoid snafus like this

donaldball21:12:02

(fn [acc x] (cond-> acc (odd? x) (assoc x x)))

jalvz21:12:31

but thanks for the tip

donaldball21:12:29

ha ha yeah, there are symbols that don’t play nicely with search engines

donaldball21:12:44

doc and source are two of my best friends

noisesmith21:12:31

well, that's something actually missing from clojuredocs (unless the link was formatted wrong)