why does IQuery (query [this]) get passed 'this'? In what case would it be used?
Is it for dynamic queries? I read on the slack log some mention of dynamic queries, but I'm not sure what it means, and what is its use case
@urbank: I don't know either. If you figure it out, please let me know.
I do know that I often see [_] and it seems like it's main use is taht the (query ... ) can depend on the defui component
@mitchelkuijpers : someone else told me that was "impossible" becuase only the Root node makes queries
so therefore, the only use is that it tells the Root what attributes it wants
furthermore, iirc, this function is "static", so it's further evidence it can't actually depend on the value of this
If you add a console.log in query you will see it get's called many times, but you are right that it needs to compose to root
This is my understanding: when Root needs to render, root needs to get it's data to gets its data, it submits it query to get the query of Root, we call all the children of Root to get their queries (and this is when the query function gets called) == it is my understanding that besides the above, query is NEVER called is that correct ?
@qqq That's my understanding as well. The component query gets called when its composed into the Root query.
@mitchelkuijpers I'm still having a bit of a hard time reconciling generic components and not having dynamic queries which would tell the component which data it should care about.
@urbank: I couldn't figure that out either, which is one of the reasons I switched over to datascript for my db.
@qqq Yeah, datascript basically lets you do anything from anywhere, because it's literally just a bunch of datoms, so all you need to change some piece of data is the global unique id of that data. However, I found some issues with performance when 'benchmarking'. It's possible that I was just doing something very wrong. You've had no issues with performance?
On the other hand, I'm pretty sure there's a solution to the generic component problem in untangled, I just don't understand it
@urbank: what type of performance issues?
for writes, I expect it to be 6x slower since it has to update every index
for queries, as long as the ordering is not stupid, I expect it to be drastically faster since it doesnot have to linearly scan all objects
@urbank: with no intention to offend -- have you worked through the first 4 sections of http://www.learndatalogtoday.org/ ? I found it very useful for mentally thikning about how the order of clauses affect the speed of a query
None taken! I have actually gone through that. I think I just had a lot of queries. There was one q which got all the entity-ids that I wanted, and passed them to subcomponents where each of the subcomponents did a pull for the attributes it cared about
I was also using Posh (if you've heard about it)
Is Posh the one where it tries to infer which datascript queries need to be rerun automatically?
That seemed way too magical for me.
Yeah, that one
I wonder if that's what's making it slow.
I[m using https://github.com/omcljs/om/wiki/DataScript-Integration-Tutorial at th moment.
And my strategy is: I write my own datascript querieis; I think very hard about how to make them fast, and I don't do any other optimizations.
Basically, my goal is : queries = lookup 1 index, get items, then just minor filtering on them;
I'm completely convinced that well organized queries should be as fast as hand written code
My top level component had a query which got all entity ids of entities with some attribute [:find ?eid :where [?eid :attr]]
this means that it had to rerun (according to posh) every time one of those entities changed
triggering a ton of subsequent pull queries ... so in retrospect I suppose it's no wonder it was slow ๐
The reason why I believe datascript can be faster in the generic case, is consdier todomvc where we want to filter on all "completed" tasks
in a simple store, we have to (1) run through all todo items (2) check if it's "completed"
in datascirpt, it's [:find ?eid :where [?eid :todo/status :completed]] and since there's an AVE index
this is equiv to (get-in AVE [:todo/status :completed])
Have you looked at union queries and the new routing in Untangled?
I can tell you from experience that datascript is way slooooower then the normal app-db format
how does union queries factor in when the fundamental issue is "data is indexed" vs "data is a list" ?
http://untangled-web.github.io/untangled/guide.html#!/untangled_devguide.M15_Routing_UI
If you add the correct idents you also index your data
I seareched that page for "index" and got nothing
where does it talk about indexing?
I believe idents noramlize data, but that's very different from idnexing.
If you have a user, and you create an ident which is [:user/by-id 1]
where you store the user info
Then I would say that is an index to get the user by id in your app-state
suppose for each user, you also stored their age
and I wanted "get me all users whose age is 25"
can you do that without running through the list of all users?
if not, I wouldn't call this indexing
Then I would think in a real use case you go to the server
I don't get what's "un"-real about the above scenario.
You'l probably want pagination and stuff on users unless you know there will never be more than 1000 for example
But I don't think you'll get much out of Untangled when you want to use Datascript
Hm... I think that things like 'get me all users whose age is 25' definitely happen on the client. Well, at least in my specific use case. The data I get from the server is very general, and I have to transform it in to various views
Ah for those cases we use post-mutations to build the right views for the same data. That is way to trigger a mutation after some data is loaded to create the right views for the data.
... or just use a db query ๐
Yes or use Datascript (but then lose all the benefits from Untangled)
Yeah, I'm still deciding between the two. But I don't think I understand untangled enough yet to make a call
Untangled is what got me into trying Om in the first place, but at this point, I'm in love with datascript and can't see what Untangled offers me over just using plain transit.
It offers you: getting data from the server, error handling, a lot plumbing, InitiallAppState goodies, Nice union query handling, A routing solution, Time traveling debugger, Testing tools, A generalized query solution, Server plumbing, i18n. You have to think if you need those things and if you are willing to build these yourself. And what do you do when you work with multiple developers and they have to use your homegrown framework.
I'm using google app engine datastore on server side
I'm using datascript on client side, and transit to communicate.
I don't see all the plumbing, InitialAppState, union query, routing ...
the replay debugger is nice, that I do miss
http://untangled-web.github.io/untangled/guide.html#!/untangled_devguide.F_Untangled_Initial_App_State this is the initial app-state (this is untangled not om.next)
http://untangled-web.github.io/untangled/guide.html#!/untangled_devguide.H_Server_Interactions and these are the server interactions from untangled
http://untangled-web.github.io/untangled/guide.html#!/untangled_devguide.I_Building_A_Server You have to create your own server and then add a om.next parser somewhere
I found that with transit/cljs-ajax this isn't nearly as big a deal as people claim to be.
That's what people to me -- and I figured out I don't ahve to use om.next on the server side, I can just transit /cljs-ajax ... and write to my gae datastore.
We should proably move to #off-topic
It's probably not very polite of me to be bashing untangled in#untangled
Yeah I was just trying to prove a point what Untangled fixes for you. I don't think you are bashing it. But I do want to make sure that you get what you are missing out on when you use om.next without untangled. (I started out with om.next and then later learned about untangled) ^^
I thikn untangled makes this assumption that "you have to build on top of om.next for client/server", and I'm now convinced: I can just use datascript for client, gae datastore for server, and cljs-ajax/transit really isn't all that bad for communication.
Also, om.next's query/db format seems very convoluted compared to datascript/datomic's eav store + datalog as query language.
I would not recommend that to be honest, but you are free to do what works best for you.
Anything in the above that would be a bad fit for untangled?
I'm not quite clear how much of that derived data should actually have some form in the database, or how to easily swap generic components.
This seems like a very good fit for untangled have you seen: http://untangled-web.github.io/untangled/guide.html#!/untangled_devguide.C_App_Database
@mitchelkuijpers Yes, but I have two unresolved questions. If there's no real structure to my data, would all the structure be given 'on the fly', with the component just getting a list of all the friends, or would some mutation create an entry in the database that would correspond to my example derived state.
The second question pertains to generic components. And I probably don't know the answer because I don't quite understand those.
If a multiselect is a generic component with a query [:ui-select/options]
how does that map onto the :friends-id list? Where it isn't actually a list anywhere, it's just derived from the fact that Tom has these 3 friends
Most Generic components don't have queries you probably don't need them for those. You can get the iinfo in a parent component and then give the generic component the :ui-select/options
And the structure in your app-db will be given by using idents. When you load data from the server it will normalize that based on your idents. http://untangled-web.github.io/untangled/guide.html#!/untangled_devguide.D_Queries there are some examples of idents in this devcard
I see. So I go to a different view of the flat data, would I create the structure on the fly, or create it in the app-db via some mutation? Also, in the untangled ui library, the calendar has a query, and Tony Kay indicated that this is useful for more complex components, because you can then affect them from anywhere in the app (click anywhere to hide some popup)
The idents get created by creating the right queries with components. Have you followed the Untangled tutorials?
I've seen the video tutorial, and read the untangled devguide where it pertains to the client-side. Maybe the relevant part went over my head. I'll definitely refer to it more.
No problem at all just keep asking, I would recommend to build a small POC. You will miss stuff when you are just following the tutorial or a video
Thanks! Yes I'm attempting a POC at the moment. Got stuck at generic components with queries, and merging their queries with something like an EditPerson component. EditPerson's query is composed into the Root query, and just specifies which attributes of a person to extract. So query: [:name :age :dark-secret]
So if :dark-secret needs to be edited by some DarkSecretEditor, and that component has its own query, how would it be composed into the EditPerson query
Since the data for generic components lives in top level tables (if I understand correctly)
would this be a use case for links? ( [:dark-secret-editor/by-id '_] )
what exactly would the DarkSecretEditor hold, since it's just editing the Person's :dark-secret?
Or more concretely, since the calendar queries for :day :month :year, this means that there's a calendar table in the app-db, which holds these 3 values
but what if these 3 values correspond to the birthday of some Person
so Person has a :birthday field which is at some point controlled by Calendar
how does this work?
I have never used the calendar component to be honest
Give me a second to see if I understand you correctly
Ah so the basic thing is you have a person and you want to change his birthday with the calendar component right?
But you want to change it when someone presses on save or submits a form, something along those lines am I correct?
you could have a {:dark-secret-editor {:who [:person/by-id 1] :form/state {:secret "foo"}}}
And when you change from which person you are editing you can change the :who
and clear the :form/state
And if you want to save them you merge the form state into the [:person/by-id 1]
Oh, sorry I had to go somewhere for a moment. Yes, you understood correctly. It's an example though. Basically some generic component which has some state in the app-db is changing some other thing in the db
No problem, and your dark secret editor could have a query of [{:who [:secret]} {:form/state [:secret]}}]
Does this make sense?
Right, that makes a lot of sense!
And to make some more generic component (such as a calendar), which doesn't have a reference to what it's editing, you would just wrap it into another component that holds the reference, and the calendar's state?
Yes, that is exactly what I would do
I think you get it!
Great, each time I pop over here and ask some questions it gets clearer. ๐ Thanks very much for the help and your time!
No problem!
@qqq if you want to use Datascript, Untangled is not for you. Agreed.
Iโm doing a data-fetch/load
using an ident with the target being that same ident. Putting a watch on the state atom and observing the state transitions for the entity under the ident, I see it correctly change to a load marker, then nil
, then the loaded state, but then itโs set back to nil
again.
If anyone has any hints for where to look to resolve that, it would be appreciated. Iโm not super familiar with the load marker lifecycle.
So, target was really meant to target the insides of a node, not a top-level table entry. The query itself does that.
I'm guessing you're using a link query and want a load marker as well?
E.g. some component is doing a query of a specific table entry that you want to load dynamically?
We have a parent component that is trying to dynamically load a child by id.
ok, so do it this way:
1. In the parent's query, add some made-up keyword that points to the correct type:
[{:the-child (om/get-query Child)}]
2. Make sure the parent has an ident, so you know where it is
3. Run a load with query params: (load [:child/by-id 42] Child {:target [:parent/by-id parent-id :the-child]})
I think that should work
Should :target
place the ident of the loaded data at the :target
location?
yes, that is what it is for
normalization still applies
(load-field this :the-child)
is also a nice shortcut for this use-case, though then your server has to understand a query based on the parent's ident
(e.g. what you'll get on the server is [{[:parent/by-id parent-id] [{:the-child (om/get-query Child)}]}]
load-field
works well for us, however Iโm not seeing df/load
getting the ident of the loaded data and placing it at :target
It is possible the ident-based load case has a bug. I was pretty sure it was tested in the cookbook load-samples, though
could be wrong. Lots of combinations are possible
It could be I only tested something like reloads, and never worked out the logic for an ident-based load that needed edge fixes in the graph
Ok great, Iโll start there.
Thanks!
If you're seeing a problem on ident-based load
, don't assume it is you.
the load marker logic might not be right for that case
Ah, looks like when default-target
here is an ident, itโs wrapped in another vector. Then we do get-in @state-atom default-target
where default-target
is [[component/by-id <id>]]
which is nested too deep.
Here I conditionally wrap the return from data-query-key
in a vector, covering the case where the query key is an ident instead of a keyword.
https://github.com/untangled-web/untangled-client/pull/65
๐ Hello! I'm trying to wrap my head around queries and database structure
In the Person
/`PeopleWidget`/`Root` example here http://untangled-web.github.io/untangled/guide.html#!/untangled_devguide.E_UI_Queries_and_State
The Root
has a query which evaluates to [{:people [:people/name]}]
. That makes sense, given the database below it where :people
is a vector of "person" maps.
What if I wanted :people
to be an "Ident-able" table? Instead of {:people [{:id 1 โฆ} {:id 2 โฆ}]}
, I'd have {:people { 1 {:id 1 โฆ} 2 {:id 2 }}
Then the [{:people [:people/name]}]
join would break
I'd like to get the same result as I'd get in the example, but also have :people
be a "table" that can be accessed via Idents. Is that possible?
If youโre normalizing the โpersonโ maps, youโd have a top-level key in your app-db that is something like :person/by-id
.
Then you could query [:person/by-id]
to get the raw table back.
So the app db would look like
{:people { 1 {:id 1 โฆ} 2 {:id 2 โฆ } }
:person/by-id [{:id 1 โฆ} {:id 2 โฆ}]}
Or if not, what data would go under the :person/by-id
key?The app-db would look like this
{:person/by-id {1 {:id 1} 2 {:id 2}}
:people [[:person/by-id 1] [:person/by-id 2]]}
ident
โs are resolved using get-in
, so if you have an ident that is [:person/by-id 1]
there must be a :person/by-id
top level key in your app-db, with itโs value being a map containing the key 1
. The ident would then resolve to the value of the key 1
.
If I wanted to set that up in initial-state
, I presume I would do that on the Root
component. Would I have to manually specify both the :person
and :person/by-id
values? I'm pretty sure there's a way to not have to manually keep the two sets of data in sync, but not clear how that works
@tony.kay Is it possible to add a filtered-list-input to the possible input types supported with Forms. It would be nice to allow the user to key in a value and have the filtered list filter the results to the value entered and if there was no match in the filtered list then the input value would be the value persisted. Otherwise, the the id of the value which matches the filtered list item would be persisted?
Any kind of form field you can imagine it easily added to form support
how you render it is up to you, and how you interact with it is also up to you
All forms care about is that you've declared some field of the entity to be form data. If it isn't a subform, you could render an alternate control (e.g. your filtered list) and use callbacks to populate the field.
The "field" in this case is just a prop on your entity. Nothing special about it at all
Right. That makes sense. I will render a filtered list but the text input would be under form support.
Say you wanted to use the ui-calendar
component to edit a date. The calendar itself has a query and mutations, and a callback. In that case you'd compose the calendar into your "form" and use the callback to update the date.
The form support itself is about state management, with some nice add-ins for common cases.
@gardnervickers Ah, so you're saying when you use an explicit target that is an ident, we get the wrong thing.
Actually no sorry
I see the problem. I'm a little concerned about the solution. I'm still not convinced we should be "targeting" something that will already normalize to the correct place.
I.e. relocate?
should be false on an ident-based load
I misread the question. When Iโm using a path like you suggested above, but loading from an ident, the default-target
ends up being [[<ident>]]
instead of [<ident>]
So (get-in state [[<ident>]])
always is going to be nil.
right, but an ident load should not be "targeted"
so the swap!
should not go in that case, and you are right, it is going an screwing things up
I thought you were suggesting this above
(load [:child/by-id 42] Child {:target [:parent/by-id parent-id :the-child]})
OH. Right
I see.
still, isn't the right fix, since the dissoc should not run. Relocate is meant to move idents, not objects...you don't want to dissoc, though I guess it wouldn't hurt, since there is no dissoc-in
I think this would be clearer, perhaps, as a cond that enumerates the cases.
Why dissoc
ever? Wouldnโt you just want to overwrite the load-marker with the value in every case?
dissoc is about loading into the root. It is simply a cleanup
(load :my-thing Comp)
adds a key to the app db :my-thing
but you wanted the value to be elsewhere, so we don't leave it around.
Ah gotcha
and it works for me because (dissoc state [:thing/by-id <id>])
does not do anything since the ident is a path into app state, not a key.
Yeah, loading a list of things, etc. Nice to send an abstract name of the query to the server, like (load :all-things Thing {:target [:thing/holder :things]})
right
doesn't hurt, but the code is already more opaque than I'd like
so a no-op dissoc makes it worse
Yea agreed
Did you verify that this does not break any tests?
Yup, I ran the firefox suite in addition to the lein tests.
ok, I'm not going to worry about the dissoc
for now. I'm doing a refactor, so I'll do some cleanup around this in a bit.
Fantastic, thanks for your help.
on 0.8.1-SNAPSHOT on clojars
be sure to upgrade Om to alpha48 and also latest cljs
Ok thanks
you can be a test subject for the new network stack ๐
This is likely problematic as well for the ident case https://github.com/untangled-web/untangled-client/blob/develop/src/untangled/client/impl/data_fetch.cljc#L311
Specifically for the loaded-callback
during the remove-markers
step.
agreed
I thought we patched that the other day
weird...did we talk about that one???
Yes but that fix didnโt actually work, probably because of the issue we addressed today.
Ahhh I see what you were saying. When data-query-key
is an ident, we donโt want to (get-in st (data-query-key ...))
and relocate that because thatโll relocate the value, not the ident.
yep
PR to set the data-path correctly for when a query is going off an ident, and retargeting loads to move the ident instead of the actual value. https://github.com/untangled-web/untangled-client/pull/66
Firefox + lein tests pass for me.
couple of quick feedbacks
via github
Hmm not seeing those on the PR
forgot to submit
sorry
@gardnervickers So, on load markers. I think we need to do a bit of refactoring, actually, and specify exactly what the behavior should be in all cases. There are not that many, and I'd like this to be cleaner
@tony.kay Thanks, fixed up the PR a bit.
Ok sounds good
and more testable
Seems there are the following cases:
1. Loading an entity by ident. We could place the data-fetch marker on an existing entity, if present (since it is a map). We could potentially generate said placeholder, but then the ident
problem (nil id) is concerning. I guess we could derive the ID of the object from the query, but not the key. That would require some additional parameter on the load, which I'm a little loathe to add.
Loading by ident gives you the id though right?
yeah, but we don't know what key it goes with
Ahhh yup.
but this is only a problem if you're using link queries
(e.g. pulling the thing into some arbitrary component by ident)
In 99% of good cases, you'd link the graph together and have a real UI component pointing to the loaded thing
The queries like [:toplevel/key โ_]
?
in which case :target
would be some prop on another obj
Yes
well
[{[:obj 3] [:a :b]}]
which you can do, but I would not recommend it
I mean, you'd need dynamcic queries
and the headaches that go with those
So, in that case we never want to put a load marker in a table. I think we just make that a rule.
you have to do your own marker if that's what you need
Oh I didnโt even know the client side parser supported that format of query.
sure, works fine. As do dynamic queries. Standard Om fare, and we use db->tree
from Om for reads, so it all works. Parameters are the thing you cannot use, but not because the syntax doesn't work
just because we give you no hook to process them. Forced trade-off
Write no read parser, get no client-side query params...but not in the dyn query sense
ug. terminology
[{:a ?subquery}]
actually works in Untangled
but [{(:a {:p 1})}]
ignores the param
and just reads prop :a
the prior is a "query param", and the latter is a "parameter on a property in a query" (or query param for short ๐ )
You can have a remote send the latter by messing with the AST in your mutation (common)...server can use them just fine
ANYHOW
back to the cases
1. Loading via ident. If you give a target, that is where the load marker goes, else you don't get one.
2. Loading via top-level key: load marker goes on top-level key. Again, all ok
3. Loading via top-level key with target: marker goes at target
am I missing any?
Does #2 get a marker if target is not specified?
well, load-field
is a utility auto-generates a query based on the parent's ident to populate a sub-field. So, it is technically (1), but with a query focused on the field.
yes, 2 is ok, since it isn't a table, just a root graph edge
So load-field
on [:something/by-id 5]
on :field
:foo
puts itโs load marker where in app state?
at [:something/by-id 5 :foo]
?
sorry
I know I confused you there
I was trying to remember
(load-field this :comments)
: The :comments
are on a subfield of this
so, the query is [{ (om/ident this) (focus-the-query-of this :comments) }]
The load marker shows up on the :comments
field of whatever object "this" is
For load-field, you're loading some additional bit of something you already have
like a blog post
but you only want to load some focused bit of it
[{[:blog-post 3] [{:comments (om/get-query Comment)}]}]
But for that case, this
could be an ident which resolves to a table entry, which we end up sticking a load marker into for :comments
. Or does the โno load marker in a tableโ rule only apply to load markers being top-level elements in a table.
no load marker as an entry in the table
Gotcha
everything is in a table in a normalized database ๐
except for the root edges, and those are keys -> idents
and any scalar values you plop in the "root node"
like :ui/react-key
Oh, I guess the load-field
case is a special one...since the load marker goes one level deeper than the others
the ident we're using is not technically the thing we're loading...or should it be?
conceptually, we're loading one or more of some children, so I've typically thought of that as loading the children. But you could also think of it as loading the "remainder" of the thing you've got.
I think it is this use-case that makes most of the logic a mess ๐
it's a really useful one, though
and the one that motivated load markers to begin with
Yea, we most often have an entity partially loaded and want to load the rest of it. For example, when doing initial page load with a blank app-db, we can gather at least the desired entity id from the URL fragment. This gives us the entity id which we can run a mutation and put in our app-db.
Then we try and load the โremainderโ of the entity with load
.
so you're treating it like a "refresh"
instead of an initial load
Yes, for the ident load case.
For every other case, we want a list of things so we use a server property.
So itโs either loading [:user/by-id <id>]
or :users/list-of
.
and your UI is querying via a link query?
or do you have some component that has [{:current-user (om/get-query User)}]
[{:current-user (om/get-query User)}]
ok, so that is your target ๐
Yup!
But weโre still hitting the server with the query [{[:user/by-id <id>] (om/get-query User)}]
You could even use load-field
with that...it supports :params
(load-field thing-with-current-user-prop :current-user :params { :user-id 5 })
And pass it the subquery as params?
would send the query [({:current-user [props of user]} {:user-id 5})]
, and your server parser would see the params
in the read keyed on :current-user
oh wait
there's another join in there
so load-field might not be best
since it would have the parent component composed in
(load this :user User {:target [:parent :comp :current-user] :params {:id 5 }})
where :user
is now the join key the server sees. Just have to make sure it doesn't collide with any state of interest already in the root of app db
and params can be used to pass the ID of interest
now you'll get the load marker as you want it
Thatโs what we were doing originally
why changed? Hopefully not because I told you ๐
Seeing the ident support in df/load
made more sense. That way it was just (df/load this (om/ident this) (om/react-class this))
for refreshes
Ah, I see
so, I would be OK with putting in a load marker IFF the entity already looks to be in your db
and in your case this would work, since you're pre-populating with something that looks like it
actually, (df/refresh this)
sounds like a nice util function with just that signature
OK, so I'm going to re-enumerate the cases:
1. Loading a field is a special case. Load marker goes in field of parent, and that parent's ident is what is used for the query. 2. Load via top-level key: Load marker goes at key (or target if specified) 3. Loading via ident: Understands that we're loading or refreshing an entry in a table. If there is already a map in the table, put the load marker on the object already in the table (and the graph will resolve it for the UI). Target is always ignored for this case. Do a post-mutation if you need to integrate the ident into app state. (or a pre-mutation if you want to see load markers for newly loading objects).
Perfect
#3 works extremely well for us.
k. patching that now
I've found a strange behaviour change between untangled client 0.7.0 and 0.8.0 To do a login I'm using a google login that is checked on the server and then saving a jwt sent from the server in a cookie. I then do follow on reads in the transaction to set/check the client app state to show login and current user - my transaction looks like this:
#(om/transact! this
`[(login/attempt {:jwt ~(om/tempid) :id_token ~(-> % .getAuthResponse .-id_token)})
(untangled/load {:query [:login-sequence] ;; this is a dummy query to sequence the post-mutation
:post-mutation login/set-cookie})
(untangled/load {:query [:logged-in? :current-user]
:post-mutation login/complete})])
I can see the cookie being set using goog.net.cookies and the correct value returned from the server in the app state from the om/tempid handling. The problem is that the last untangled/load call does not provide the cookie in the request using v0.8.0 whereas they did in v0.7.0. This is visible in the request headers in the browser network inspector.So, you're indicating that post-mutation did not run?
The login/set-cookie post-mutation does run and the cookie is set, it appears like the next remote load (from the last untangled/load doesn't contain the cookie).
oh, are you expecting the cookie to be set between these two calls?
Yes - is that not the expected behaviour?
hm. well, mutations go before reads, so your attempt will happen on a separate network request
Then the loads are ganged together in a separate request (they go together on the net)
I'm not sure why anything that changed would have affected something like this if it was already working ๐
so , I would not have expected what you wrote to work because the post mutations always go after the queries have run, and both go at the same time...
so no, I would never have expected that to work as described.
reads have been combined since day one
Can you have login/attempt
set the cookie?
I'll try that
attempt is a remote mutation, right?
and you have the value you need before it runs, or no?
if no, then you could install a mutation-merge
function at startup, and capture the attempt
merge to handle a return value
attempt is a remote read, and I generate my own jwt token on the server which is then placed in the app-state (using temp-id) hence the dummy read - I was attempting to follow the pattern at the bottom of M40 in the devguide
sorry mutation not read
I see. OK. So bullet 3, essentially.
yes
So, in a single transaction, the reads will go over the network together, and the post mutations will run (after they've returned)
so it should have never worked to expect the last load to have the cookie if the cookie was set in the middle one
The example you point to: The assumptions is that you already know who the user is. If the remap came from the server. Otherwise the server should throw an exception which will kill the transaction on the client.
Ah, okay good to know about the ordering, I'll see if I can get something different to work
clarification: the assumptions is you know what the user told you about themselves. The login mutation is trying to verify that (and using a tempid as a way to get a response).
If you refuse to remap on the server, the the client will still have a tempid, which you could use to detect that something went wrong
If you need identity info from the server after doing the login sequence (and that requires a cookie), then I'd probably use a mutation-merge
and just return the info from the login
The tempid remap WILL happen before the post mutations on the reads
looking a bit further, I'm just trying to understand the ordering of things that happend with :post-mutation - I've setup a simple test case like this:
#(om/transact! this `[(untangled/load {:query [:something]
:post-mutation test/pm1})
(untangled/load {:query [:something]
:post-mutation test/pm2})
(untangled/load {:query [:something]
:post-mutation test/pm3})])
Where test/pm1,test/pm2,test/pm3 just output a string to client consolt (pm1, pm2, pm3) - should I expect the output to be 'pm1','pm2','pm3' or is the ordering undefined?the order should be preserved
but realize that the loads ALL run the networking part, and THEN run all 3 post mutations
the networking is done before the first post mutation
actually, you could confuse yourself with this particular example, since they all have the same query
This is a special case
The return value from the server has to be a map, and :something
can only appear once in such a map, so Untangled should actually do these in perfect sequence as you were thinking (it must serialize them because otherwise they'd stomp on each others return values)
change the queries to :a :b and :c
, and you should only see one network requext
@tobias ^^^
@gardnervickers So, I've got a patch. I think this is right: :target
really only applies on queries that load into the top-level (root)
which simplified a bit of logic
I added tests, and updated the relocation and marking code. I have not tested it on a live app, but I can push a snapshot
I'll test live against that and would love to hear back from you
0.8.1-SNAPSHOT on clojars. Testing for regressions against cookbook.
@tony.kay Fantastic, Iโll check that out on our app tonight.
Okay, I now understand the network stuff and the way it's batched I think, the strange thing is that in 0.7.0 I see pm1,pm2,pm3 and in 0.8.0 I see pm3,pm2,pm1 - even with seperate queries BUT, I think I'm realising that in practice with the correct usage that this won't make a difference to the final outcome of the transaction
Yeah, I was not aware the order changed. I did rework a bit on the network stack.
I cannot think of what I did that would have revered them
is that with out without the conflicts in naming?
yes, reversed when the names are not conflicting
hm, but not in 0.7.0, eh?
it shouldn't matter, but I don't personally like things like that changing
vectors imply order
@gardnervickers It seems to check out for me. I also added a df/refresh!
function for your specific case
I'll run through the diffs and see if I can find anything
could be in mark-loading
I had to revamp that for the multiple remotes
I you look at the actual network request, is it in the right order?
i.e. is it just running the mutations in some arbitrary order?
because the post-mutations will run in whatever order the responses are seen in, and the response is a map
Your server code can affect that
the post mutations go with the query. I don't think I changed anything. I think your response is causing the order of the post mutations
@tobias ^^^
@tony.kay git bisect says this is the first commit where it changes - bbae5ea568291c8589bf6dcae55863565b739a95 - Added support for multiple remotes - Updated defmutation to allow definition of multiple remotes interactions I don't have anything but a print on the server side, it's the ordering of the sends from the client that is being reversed
thanks for doing the research. I'll look at it
well, the item filtering down end up using a set, which doesnt have guaranteed order
but that was how it was before
the hashes of the data items could have changed, which would cause the set to reorder
the data item markers in load tracking, that is
which they would have
So, technically this "broke" some time ago when the query collision avoidance was added