clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
NoahTheDuke 2021-04-27T14:52:24.075400Z

Reloaded Workflow question/discussion: I have moved my app to Integrant and like it a lot so far, but I'm starting to rub up against some pain points of "no globals"/"no implicits".

NoahTheDuke 2021-04-27T14:52:26.075600Z

Previously, in my db.clj file, I had a function called connect that would defonce an object that contained the connection to the database. I would call this during initialization. Then I could freely import that db object into whichever namespace I needed it and use it directly in any functions. Felt very similar to how various other languages do database connections (at my job we use Knex and Objection.js, but Rails is similar).

NoahTheDuke 2021-04-27T14:55:18.078500Z

Now I create the database connection in the system and it's held inside of the returned system object, which I then pass into my ring app (which further passes down into my websockets/sente handlers), so every function that uses the database has to destructure/`get-in` the db object from the request or it has to be passed in from one of those functions as a new arg. I have it all working, but it feels significantly more cumbersome than being able to simply require and use.

NoahTheDuke 2021-04-27T14:56:26.079100Z

am i doing this correctly? Should it be working like this?

2021-04-27T14:57:47.079300Z

the idea is to have ring app included into system, then your db can be simply added as a reference to constructor of ring app

NoahTheDuke 2021-04-27T15:00:28.079500Z

That's how I have it:

(defmethod ig/init-key :web/app [_ {:keys [db]}]
  (make-app db))
and then
(defn wrap-db [handler mongo]
  (fn [req]
    (handler (assoc req :system/db (:db mongo)))))

NoahTheDuke 2021-04-27T15:02:44.079900Z

and then the REST handlers look like this:

(defn login-handler
  [{db :system/db
    {:keys [username password]} :params}]
and the sente event handlers look like:
(defmethod ws/-msg-handler :lobby/delete-game
  [{{db :system/db
     {:keys [username isadmin ismoderator]} :user} :ring-req
    {:keys [gameid]} :?data}]

NoahTheDuke 2021-04-27T15:03:37.080400Z

it's not bad, just cumbersome, you know?

dharrigan 2021-04-27T15:06:29.080600Z

I have a similar setup with my applications (although I use JUXT Clip, not integrant). I pass into my ring handler the setup (that has been configured via juxt clip, which includes references to connections to the db etc). I then pass that "opaque deity" collection into each function that needs it (and passes it along if required).

2021-04-27T15:06:53.080800Z

you can add all the handlers into the system and use db reference everywhere its needed

dharrigan 2021-04-27T15:07:03.081Z

I did think it was cumbersome, but you know what it brings? A very easy way to test things, you can construct the application configuration (map) to hold whatever values you want for testing...

dharrigan 2021-04-27T15:07:35.081200Z

and you can isolate individual functions, passing into them just the configuration map data that is useful for them.

dharrigan 2021-04-27T15:08:11.081400Z

I have an example of using juxt clip, a database and passing the data long here: <https://github.com/dharrigan/startrek>

πŸ‘Œ 2
2021-04-27T15:08:36.081700Z

I don’t think one should use configuration map as is in any function outside of implementation for ig/init-key function

dharrigan 2021-04-27T15:08:41.081900Z

<https://github.com/dharrigan/startrek/blob/master/src/startrek/base/api/router.clj> the map is called app-config

dharrigan 2021-04-27T15:11:08.082100Z

Here's an example of where I pull out the configured database connection to pass into next.jdbc for it to do its thaannng.

dharrigan 2021-04-27T15:11:10.082300Z

<https://github.com/dharrigan/startrek/blob/be32d9d27fe7da51681d57a18e0de904d0e6af38/src/startrek/components/starship/impl.clj#L58>

NoahTheDuke 2021-04-27T15:11:34.082500Z

i thought about adding all of the handlers to the system, saw that in https://github.com/eeng/mercurius, but that felt like even more of a restriction than how it works currently

NoahTheDuke 2021-04-27T15:11:53.082800Z

thanks for that repo, @dharrigan, i'll check it out!

dharrigan 2021-04-27T15:12:10.083Z

sure, np. it's just my approach, I hope it helps, or maybe it doesn't πŸ™‚

Tommy DeVito 2021-04-27T15:58:08.083700Z

@dharrigan are you following the polylilth arch in your starship example?

NoahTheDuke 2021-04-27T15:59:33.083900Z

@admin055 i read through that one when i first started this process! it wasn't super helpful because of how reitit and compojure differ, but I plan on moving my app to use reitit and will be re-reading your example repo when i do that

πŸ‘ 1
dharrigan 2021-04-27T16:20:41.084200Z

@tom.devito22 I've borrowed some things, but rejected others whilst I'm still evaluating it

dharrigan 2021-04-27T16:20:59.084400Z

I'm not convinced yet by the approach

Tommy DeVito 2021-04-27T16:23:04.084600Z

thanks :thumbsup:

2021-04-27T18:16:30.086300Z

I found a problem with update-in : it doesn't behave as one could expect when ks is [] In its implementation, it is assumed that ks contains at least 1 key. However, the documentation of the function doesn't mention it.

2021-04-27T18:20:57.086500Z

I noticed that assoc-in also assumes at least 1 key in its implementation. I guess that there is an implicit expectation that the user doesn't use them with no keys in ks .

2021-04-27T18:24:29.087600Z

What would you expect update-in to do if ks is []?

2021-04-27T18:25:26.087900Z

passing m directly to the function in parameter.

2021-04-27T18:25:40.088100Z

Instead, it passes nil

NoahTheDuke 2021-04-27T18:26:40.088300Z

thanks for the input, everyone. been thinking about this a lot recently and all of the responses and ideas are helpful

alexmiller 2021-04-27T18:32:22.089Z

use update in this case

2021-04-27T18:33:40.089200Z

@alexmiller I ran on this situation by having a computed value for ks.

2021-04-27T18:34:18.089400Z

I will write my own version of update-in to support this case, but I wanted to report the issue, still.

alexmiller 2021-04-27T18:34:40.089700Z

it's been reported and declined in the past

πŸ‘Œ 2
2021-04-27T18:34:46.089900Z

ok then.

alexmiller 2021-04-27T18:34:56.090100Z

https://clojure.atlassian.net/browse/CLJ-1623 maybe?

πŸ‘ 1
2021-04-27T18:35:33.090400Z

Yes, that's exactly this.

2021-04-27T18:37:54.090700Z

Maybe the docstring of update-in could mention that the path requires at least 1 segment.

Helins 2021-04-27T18:42:20.091800Z

It's true the docstring advertises a seq and not a list, yet this made me uncomfortable

(list? (list* [1 2]))

;; =&gt; false

alexmiller 2021-04-27T19:03:47.093700Z

it's unusual to use list* and it's an old function so pre-dates some later rework of sequences that occurred

πŸ‘ 1
alexmiller 2021-04-27T19:04:00.094Z

but it does what it says :)

2021-04-27T19:12:44.094100Z

It isn't as official as the built-in doc strings, of course, but checking whether there is an example or note about this on community-contributed docs page on http://ClojureDocs.org , and adding a new one if there is not already, would be good: https://clojuredocs.org/clojure.core/update-in

2021-04-27T19:12:50.094300Z

And anyone can do that.

πŸ‘ 1
2021-04-27T19:45:56.094500Z

You're welcome. πŸ‘

borkdude 2021-04-27T20:12:22.095600Z

I probably use list? less often than list* (you almost always need seq?)

ag 2021-04-27T21:14:25.099200Z

I need to write a function. The header so far looks like this:

(defn update-entities
  "Updates maps in a coll by applying functions one by one to items in the
  coll. Each function takes an old value and returns new value of an
  item. Number of updated entities in the coll would always be equal to number
  of functions passed. The total number of maps in the coll, remains the same."
  [ents f &amp; fs]
Every single idea I have right now about how to write the actual body of this function feels lame to me. Anyone wants to give it a shot? While my lazy and slow brain gestates a "perfect" solution.

walterl 2021-04-27T21:19:57.099500Z

Are all functions applied to each map, or is f applied to (first ents), (first fs) to (second ents), etc.?

alexmiller 2021-04-27T21:19:58.099700Z

kind of a map juxt kind of thing

ag 2021-04-27T21:25:47.099800Z

every function applied to a single element in the coll f1 to the first f2 to the second, and so on. if there are fewer functions that elements, remaining elements won't change

walterl 2021-04-27T21:28:32.100Z

As in (map #(%1 %2) fs ents)?

walterl 2021-04-27T21:28:44.100200Z

It just won't satisfy that last part: > if there are fewer functions that elements, remaining elements won't change

Michael Gardner 2021-04-27T21:29:25.100400Z

(map #(%2 %1) ents (concat fs (repeat identity)))

πŸ‘ 3
walterl 2021-04-27T21:30:33.100700Z

Ah, a positive use of concat. Nice, @michael348 πŸ‘

ag 2021-04-27T21:31:56.100900Z

Whoa. How did you even?... man, you folks are so awesome.

ag 2021-04-27T21:37:07.101300Z

The actual mechanics requires fewer parts (even fewer parentheses) than the number of words in the docstring for the function. So cool.

walterl 2021-04-27T21:38:12.101500Z

That's often a sign of a good docstring πŸ˜‰

ag 2021-04-27T21:40:18.101700Z

Yes, I'd like to be one of those coders who write more English and more tests and less the actual code that's meant for the execution.