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".
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).
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.
am i doing this correctly? Should it be working like this?
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
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)))))
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}]
it's not bad, just cumbersome, you know?
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).
you can add all the handlers into the system and use db reference everywhere its needed
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...
and you can isolate individual functions, passing into them just the configuration map data that is useful for them.
I have an example of using juxt clip, a database and passing the data long here: <https://github.com/dharrigan/startrek>
I donβt think one should use configuration map as is in any function outside of implementation for ig/init-key function
<https://github.com/dharrigan/startrek/blob/master/src/startrek/base/api/router.clj>
the map is called app-config
Here's an example of where I pull out the configured database connection to pass into next.jdbc for it to do its thaannng.
<https://github.com/dharrigan/startrek/blob/be32d9d27fe7da51681d57a18e0de904d0e6af38/src/startrek/components/starship/impl.clj#L58>
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
thanks for that repo, @dharrigan, i'll check it out!
sure, np. it's just my approach, I hope it helps, or maybe it doesn't π
@dharrigan are you following the polylilth arch in your starship example?
@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
@tom.devito22 I've borrowed some things, but rejected others whilst I'm still evaluating it
I'm not convinced yet by the approach
thanks :thumbsup:
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.
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
.
What would you expect update-in
to do if ks
is []?
passing m
directly to the function in parameter.
Instead, it passes nil
thanks for the input, everyone. been thinking about this a lot recently and all of the responses and ideas are helpful
use update
in this case
@alexmiller I ran on this situation by having a computed value for ks
.
I will write my own version of update-in to support this case, but I wanted to report the issue, still.
it's been reported and declined in the past
ok then.
Yes, that's exactly this.
Maybe the docstring of update-in
could mention that the path requires at least 1 segment.
It's true the docstring advertises a seq and not a list, yet this made me uncomfortable
(list? (list* [1 2]))
;; => false
it's unusual to use list*
and it's an old function so pre-dates some later rework of sequences that occurred
but it does what it says :)
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
And anyone can do that.
You're welcome. π
I probably use list?
less often than list*
(you almost always need seq?
)
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 & 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.Are all functions applied to each map, or is f
applied to (first ents)
, (first fs)
to (second ents)
, etc.?
kind of a map
juxt
kind of thing
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
As in (map #(%1 %2) fs ents)
?
It just won't satisfy that last part: > if there are fewer functions that elements, remaining elements won't change
(map #(%2 %1) ents (concat fs (repeat identity)))
Ah, a positive use of concat
. Nice, @michael348 π
Whoa. How did you even?... man, you folks are so awesome.
The actual mechanics requires fewer parts (even fewer parentheses) than the number of words in the docstring for the function. So cool.
That's often a sign of a good docstring π
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.