This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-03-06
Channels
- # architecture (25)
- # bangalore-clj (1)
- # beginners (21)
- # boot (45)
- # cljs-dev (38)
- # clojure (272)
- # clojure-austin (7)
- # clojure-finland (7)
- # clojure-france (3)
- # clojure-italy (7)
- # clojure-japan (1)
- # clojure-russia (13)
- # clojure-spec (36)
- # clojure-uk (31)
- # clojurescript (96)
- # core-async (15)
- # cursive (16)
- # datascript (3)
- # datomic (97)
- # emacs (107)
- # hoplon (16)
- # jobs (9)
- # keechma (1)
- # luminus (1)
- # off-topic (19)
- # om (39)
- # onyx (15)
- # pedestal (3)
- # planck (22)
- # protorepl (4)
- # re-frame (20)
- # reagent (3)
- # ring-swagger (25)
- # specter (26)
- # test-check (19)
- # testing (1)
- # untangled (381)
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
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
@urbank @qqq You can use the instance of the component for dynamic queries indeed, but I never need this with untangled. And it adds a lot of complexity that I would stay away from.
@mitchelkuijpers : someone else told me that was "impossible" becuase only the Root node makes queries
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
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
Is Posh the one where it tries to infer which datascript queries need to be rerun automatically?
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
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
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
Then I would think in a real use case you go to the server
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.
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.
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.
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.
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.
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)
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
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?
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!
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.
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.
1. In the parent's query, add some made-up keyword that points to the correct type:
[{:the-child (om/get-query Child)}]
3. Run a load with query params: (load [:child/by-id 42] Child {:target [:parent/by-id parent-id :the-child]})
Should :target
place the ident of the loaded data at the :target
location?
(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
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!
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
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 }}
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?
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 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.
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]})
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
Why dissoc
ever? Wouldn’t you just want to overwrite the load-marker with the value in every case?
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]})
Yea agreed
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.
Ok thanks
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.
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.
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.
Hmm not seeing those on the PR
@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
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?
Ahhh yup.
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 ‘_]
?
So, in that case we never want to put a load marker in a table. I think we just make that a rule.
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
Write no read parser, get no client-side query params...but not in the dyn query sense
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
1. Loading via ident. If you give a target, that is where the load marker goes, else you don't get one.
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.
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]
?
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
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.
Gotcha
Oh, I guess the load-field
case is a special one...since the load marker goes one level deeper than the others
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.
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
.
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
.
[{:current-user (om/get-query User)}]
But we’re still hitting the server with the query [{[:user/by-id <id>] (om/get-query User)}]
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
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
That’s what we were doing originally
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
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
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.
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.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).
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...
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
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
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?but realize that the loads ALL run the networking part, and THEN run all 3 post mutations
actually, you could confuse yourself with this particular example, since they all have the same query
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)
@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)
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
@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
@gardnervickers It seems to check out for me. I also added a df/refresh!
function for your specific case
because the post-mutations will run in whatever order the responses are seen in, and the response is a map
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
@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
well, the item filtering down end up using a set, which doesnt have guaranteed order
the hashes of the data items could have changed, which would cause the set to reorder