0.6.0-SNAPSHOT is on clojars
I added a new implementation of load
and load-action
and am planning to deprecate load-data
and load-data-action
. The new API is cleaner, and includes a :target
parameter that can be used to avoid many post-mutations.
The target is also where the load marker will go
I'd be interested in hearing feedback. It does make a join for you, so perhaps that isn't ideal...I consider it experimental for the moment, but I'm playing with it to see if it makes sense. It is likely I'll evolve it a bit more...not sure about the required keyword yet, but I think it makes sense for "global loads"
@tony.kay i definitely think that's a step in the right direction
@tony.kay one of the patterns that I would like to see emerge in our code-base is that every table we have in our DB, there's a corresponding table in our om.next normalized DB, so there's a 1:1 mapping between both DBs
@tony.kay So apart of better communicating on my end, the pattern means loading data into idents is very common, as well as queries that join on idents
@jasonjckn Yeah, that latter use-case is one Iโm still pondering
I could make load
take a keyword or ident instead of just a keyword. But then :target
does not make sense. Maybe a third function load-entity
we originally had load-collection and load-singleton
the new load
serves well for collections and singletons where the singleton isnโt necessarily in a table. That sounds like a step backwards at first, but I think it might just be a good refinement. I donโt like swiss-army-knife functions
I jus pushed an update to 0.6.0-SNAPSHOT. load
now accepts either a keyword or explicit ident, which will allow you to load an entity into a table directly. It still needs the component query for normalization, so it really wasnโt much of a big deal.
I also fixed all open issues that had reproducible cases, and closed the ones that seemed more like noise.
@tony.kay That should be very helpful to us. Iโm playing around with df/load
right now, but having trouble getting it to work in places I was previously using load-data
.
From the docs, I would assume these two calls would result in similar behavior. load-data
correctly loads the returned idents under [:profile-list/by-id :all]
, while load
sets [:profile-list/by-id :all]
to nil.
(df/load-data reconciler
(om/get-query profile/AllProfiles)
:ident [:profile-list/by-id :all])
(df/load reconciler
[:profile-list/by-id :all]
profile/AllProfiles
{})
Should we handle setting [:profile-list/by-id :all]
to the collection of returned idents in a post mutation now?no, that should work
I did the ident thing last night before bed. I thought I tested it though
Yeah, in fact I played with it in the context of the tabbed interface cookbook recipe
(not saved there...but that was the code I was using)
@gardnervickers ^^^^
Hmm ok thanks
that isn't the way I'd do it, though
(df/load :all-profiles Profile {})
would be better (I need to make that last arg optional). Normalization will make the db table.
so, file an issue and I'll see what I did wrong, but consider the technique...loading a single thing is what the ident syntax is meant to do
With my suggested technique: the server then just returns a vector of profiles in resp to :all-profiles
What would you do if you had different lists of profiles
do they all normalize the the same table, or different?
The same
add a post-mutation that creates your various filtered list of idents, which is what you must be doing already, right?
if you want to load sub-lists:
(df/load :profiles Profile {:params {:list :x-profiles}})
(df/load :profiles Profile {:params {:list :all}})
etc
then the server will the the list parameter in params
If you're loading sublists, then you can use target to place those ident lists where you want them!
(df/load :profiles Profile {:params {:list :x-profiles} :target [:profile-widget :x :items]})
obviating the need for post mutations ๐
ahhh that makes sense now
thatโs pretty great
then you never need :all-profiles at all
just issue 6-7 loads
they'll all get batched together as a single net request by the back-end unless you specify :parallel true
fantastic
yeah, it is pretty sweet
AND the load markers will go in the right places ๐
with target
Thatโs really cool, I was getting tripped up on the purpose of ident
in a query server side
yeah, that is for loading A specific entity/row of a database
and putting in your app state table
gotcha
(df/load [:person/by-id 23] Person {})
Ahhh great, this removes a lot of the awkwardness I had going on before
Now that I think of it, using :all in that should not work. We use Om's merge for that, and it expects a SINGLE thing on an ident merge. Not sure why that was working for you before actually
entries in Om tables are supposed to be maps
For my [profiles-list/by-id :all]
I was returning a thing like {:profile-list/by-id :all :list-of/profiles [โฆ.]}
AH
yeah, you were tricking normalization to do it
so, that will remove a lot of awkwardness for sure ๐
the old joke of "doc, it hurts when I do X"....doc: "well, then don't do that"
heh for sure, thanks!
welcome
@gardnervickers Just pushed a new snapshot to clojars. Makes config
parameter optional
Great, I was going to ask about switching from the multi-arity options to the map options, glad itโs optional now.
The named parameter syntax was nice on the surface, but I kept running into situations where I wanted to compose something I had in a map with the "options", which is quite ugly when functions take named parameter multi-arity. An optional map is just easier to work with compositionally.
I'm acutally open to feedback. I don't consider the API of load
solid yet. If you'd prefer the "named parameter" notation, it is a simple-enough change.
I was more changing the internals around to use maps for cleaner stuff internally, and I propagated it out to the public API. Might make sense for the public API to do what load-data
did with params
I've actually never needed the composition aspect at the public layer...so I'm an easy sell ๐
I am all for map params
+1, its easier to create and work with programmatically, and as an end user you just have to now type 2 extra chars {โฆ}
(one char if you are using a good structural editing plugin)
k
Also wanted to mention how awesome the devcard integration is now for viewing app state
It would be pretty cool if the client included a version of the tutorial macro for using devcards with Untangled.
@gardnervickers is there a place I can read more about this devcards integration?
Just checkout the tutorial
thanks, found it ๐
Yeah, I should move the macro over
or anyone else can....PR welcome ๐
@currentoor @jasonjckn @cjmurphy @therabidbanana @wilkerlucio and anyone who is using this in production: Tony and I have been talking about using clojure(script) 1.9 for clojure.spec The only question remaining is if you (the untangled community) feel that it would be safe for production use. So, would you be okay using 1.9?
@adambros we already do ๐
:3
So we'd be more than fine with it!
I'm wanting to spec and conform all the things, so thats great to hear.
We still haven't gotten around to using spec though. Looking forward to what you come up with.
most of it is internal, but it should help with error messages and simplifying the codebase
I wrote a blog post about how we used this stack to build real-time collaboration in our product.
for the most part it goes outside untangled but I thought I'd share here in case anyone was interested
nice stuff (skimmed it)
i should write up how i did authorization in one of our products
at a high level what did you do?
we just used JWT tokens
yeah im not talking about authentication
weโre just using openid and jwt, iirc
We use 1.9
Authorization wise
Entities (or one of their parents) have an :auth/owner
ref
An :auth/owner
has a :auth/property
that points to our auth database (we limit access by property or property group)
the server code just walks up the graph from an entity until it can find an :auth/owner
and checks you are allowed to read or mutate it
weโd probably have to add more fields if we need more granular (user level) access control
but it feels pretty flexible, and we dont have to pepper every entity with :auth/owner
as some things can be inferred to be owned by one of their ancestors
@adambros I'm using 1.9 on my project as well ๐
Iโm going to go ahead and upgrade to 1.9 on my untangled branches
@adambros oh cool, that sounds more sophisticated than we we did
it felt sophisticated, but i wasnt sure how to build something that would stay flexible as it was up in the air what kind of auth we would really need for it
we just wrote a macro that looks like a defmethod
for om next mutations except it also takes in a policy function that takes in the arguments to the om next mutation and returns true or false based on authoraization
(defusecase api-mutate 'widget/delete with policy/widget-owner
[{:keys [database]} _ {:keys [id]}]
{:action #(delete-entity database id)})
(defn widget-owner
"current/organization owns this widget."
[env k params]
(let [{:keys [database current/organization]} env
{:keys [id]} params
org-id (:organization/adstage-id organization)
conn (db/get-connection database)]
(and (existence env k params)
(= org-id
(-> (d/entity (d/db conn) id)
:dashboard/_widgets
:dashboard/organization
:organization/adstage-id)))))
but i think our authorization is less complicated than yours because you have people doing surveys right?
yeah but not as complicated as you might think if you can constrain it just right
our concepts of being in a property helps a lot
you also dont have to secure taking the survey
theyโre not logged in anyway
oh i see
but yeah a blog post on it would be cool
is there a reason you are destructuring in a let instead of your params? in widget-owner
as a quick note, thereโs a PR on untangled-datomic https://github.com/untangled-web/untangled-datomic/pull/4 for: 1. Implemented IDeref on DatabaseComponent, which returns a datomic.db.Db instance. 2. Updated the query function in untangled.datomic.core to support multiple data sources of varied types.
let me (or @ethangracer) know if you have any thoughts
@adambros sloppy code review most likely ๐ (why destructuring inside let)
haha lol its fine just curious
@adambros: Yes 1.9 will be good.
@adambros thanks for asking, yes! we upgraded last week for the same reason: spec