Fork me on GitHub
#untangled
<
2017-07-02
>
cjmurphy00:07:45

I've watched that video a few times. Is one of the best for understanding things - very good video. I see that load-data does have config as last argument, which can include params. So I guess can achieve [({server-property (om/get-query SubqueryClass)} params)] by other means - in fact I can see that [state-atom server-property-or-ident SubqueryClass config] will do the job.

cjmurphy01:07:12

So -action means it is callable from within a client mutation?? That's the all important thing for me right now.

tony.kay01:07:20

@cjmurphy yes. The -action is intended to mean that. It’s really simple how it works: Calls to load basically run a mutation transaction on the untangled mutation load (don’t remember the ns). The load mutation gets processed, but what it does is stores your desired load info into an app state load queue. The mutation specifies that it wants to be remote. When the layer just above networking sees the load mutation, it instead pulls the stuff from the load queue in app state and sends those queries instead.

tony.kay01:07:43

The -action versions are the part that put the load on the queue. So, you still need to specify a remote-side, and data-fetch has a function (`remote-load`) for generating the right-looking thing for the remote side that will cause load queue processing to happen.

tony.kay01:07:27

any number of load-actions can be run (each adding an entry to the load queue), and one remote-load (as the return value of the :remote side.

tony.kay04:07:27

@cjmurphy You need a remote on it

tony.kay04:07:43

that’s what I was saying in prior comment

tony.kay04:07:59

(remote [env] (df/remote-load env))

tony.kay04:07:04

inside your defmutation

tony.kay04:07:39

load-action affects state, but Om won’t even try to go remote without a remote indicator, which is generated by the remote-load function.

tony.kay04:07:43

The doc string for load-action is pretty specific about that. Do you not have docstring help?

tony.kay04:07:22

otherwise it looks good

tony.kay04:07:34

(defmutation load-existing-rules [{:keys [sub-query-comp source-bank target-ledger]}]
  (remote [env] (df/remote-load env))
  (action [{:keys [state]}]
          (df/load-action state :my-existing-rules sub-query-comp
                          {:target  help/rules-list-items-whereabouts
                           :refresh [[:banking-form/by-id p/BANKING_FORM]]
                           :params  {:source-bank source-bank :target-ledger target-ledger}})))

tony.kay04:07:22

Mutations in Om can have multiple parts. The action is always local. It cannot trigger anything to do with networking. The other sections are names of remotes (the default name of a remote is remote). Each one indicates what should be done on that specific remote for this action. The special value true means “the same abstract thing that happened on the client`...but for load actions, you don’t want to run load-existing-rules on the server, you want to send the query. The load-action has stored the query in a queue. The remote-load returns an AST that will trigger the queue processing on that remote.

tony.kay04:07:35

technically, for loads, triggering remote-load from any remote causes queue processing, and the queue entries themselves use a :remote option to indicate which remote they query

cjmurphy06:07:04

@tony.kay The docstring even has example, so you are correct it should be pretty obvious. At the moment I just read docstrings by looking at the source (cntl+B on the function), where there is no colour coding or anything. The example is in the old pre-defmutation style and is at the top of some lines of comments. And a colon sometimes looks like a semi-colon to me. So to my eyes :remote (df/remote-load env) was just part of the comments and got missed. If it had been new style, starting with a paren, or the remote had been after the action, so with lots of whitespace around it - in either of those case it would have been hard to miss. Syntax colouring is probably my answer - I'll see about using the proper IntelliJ way of looking at docstrings.

tony.kay06:07:14

Yeah, updating the docstrings would be good 🙂

cjmurphy06:07:45

It is cnrl+Q on my Linux machine for looking at docstrings, but it doesn't have a different colour for code. Really I should have understood/researched your comment: "`data-fetch` has a function (`remote-load`) for generating the right-looking thing...". All my eyes needed was to see (remote [env] (df/remote-load env)), which was nowhere to be found. I did try (remote [_] true), but of course that didn't help.

tony.kay06:07:36

Yeah, this was one of the important realizations in Untangled: how to turn mutations into loads so that we don’t need a parser to be involved in remote queries. It turns out that you need a mutation to modify state so that your parser can trigger a load anyway in Om Next (otherwise you’d be loading at every parse…the state has to change so your parser can be intelligent about loading). So, the trick was to realize that any mutation with a remote AST will cause Om Next to try to send that over the wire, so we hack into that to transform the mutation into a query. The only downside is that composing loads into mutations then looks a little wierd.

tony.kay06:07:45

I guess there could be some macro similar to defmutation that always indicates you’re wanting to process the load queue and does it for you.

cjmurphy06:07:17

I'm going to be calling om/tranact! from within a defmutation... that's what about to try

tony.kay06:07:31

no, that is not advised

tony.kay06:07:54

mutations are the bit twiddling. Don’t embed a higher-level abstraction in them

tony.kay06:07:54

modify state, indicate what is remote. That’s all.

cjmurphy06:07:57

So I'll compose df/load-action into the threading macro on the state.

tony.kay06:07:10

that is the intention

cjmurphy06:07:22

So the other one that wasn't remote before will now become remote too.

tony.kay06:07:07

You’re writing one mutation, right?

cjmurphy06:07:20

One that calls another.

tony.kay06:07:03

I don’t see one that calls another

cjmurphy06:07:02

When a drop down is loaded with all its values - thats the first mutation. It works out the value which will be the chosen value of the drop down. Now data needs to be loaded from remote for this chosen value.

tony.kay06:07:30

ah, so the completion of one load needs to trigger another?

cjmurphy06:07:48

No - the first is not a load

tony.kay06:07:21

so, you’re trying to write a mutation that 1. sets the dropdown display, and 2. triggers a load

cjmurphy06:07:28

(as it happens in my case)

tony.kay06:07:18

ok. (swap! state (fn [s] (-> s (do-dropdown-update) (load-action :kw ...))))

tony.kay06:07:22

within action

tony.kay06:07:27

and remote-load in remote

cjmurphy06:07:04

Yes good. So the first mutation now becomes a remote when it wasn't before.

tony.kay06:07:31

what first mutation? You mean do-dropdown-update?

cjmurphy06:07:58

Yes - put the list of values in the actual drop down control.

tony.kay06:07:01

that isn’t a mutation, it is a function

tony.kay06:07:25

that is part of the implementation of the action (local only) of a mutation

cjmurphy06:07:34

It is a mutation because it is changing state.

tony.kay06:07:44

the remote-load sends an AST that just processes the network queue

tony.kay06:07:48

it knows nothing of what you did to state

tony.kay06:07:29

if you say “remote true”, then you’re asking the top-level transaction to go over the wire…still doesn’t know the guts of action

tony.kay06:07:55

by using remote-load, you’re replacing the top-level transaction with a special AST that processes the network queue

cjmurphy06:07:27

I can just try it both ways, with and without making the first mutation remote. So it sounds right from what you are saying to make the first one be remote true.

tony.kay06:07:37

(I’m using the term mutation here to mean the top-level transaction)

tony.kay06:07:51

I don’t see a first one

cjmurphy06:07:59

I've got two defmutations.

tony.kay06:07:02

so you’re just confusing me. I see defns

cjmurphy06:07:13

Both local.

tony.kay06:07:16

not in the code you posted

tony.kay06:07:28

but I assume you just elided that. So, remote true means you want to send the top-level tx, as DATA, across the wire to the server, as is from the UI

tony.kay06:07:39

if that is what you want, then yes 🙂

cjmurphy06:07:49

Yeah the code I posted is the second defmutation, that I'm calling from another one.

tony.kay06:07:05

If you’re doing a load-action, you do not want to send the top level UI tx…you want to process the queries of load-actions.

tony.kay06:07:38

but you don’t call one defmutation from another

tony.kay07:07:56

you either: 1. Write a transact that does the two in succession: (om/transact! this '[(a) (b)]) 2. Write a transact that calls one (om/transact! this '[(combo)]) and then in that one mutation you do the local updates along with load-action

cjmurphy07:07:11

I think I have to. No way round it - if not actually call, do the equivalent of calling.

cjmurphy07:07:01

Yeah I'll be doing the second, but semantically it is a call of one of the other.

tony.kay07:07:05

Why not write a query that asks for both facts you need, as a union query. Then the server can tell you everything you need with one load

tony.kay07:07:16

without loading more than you need

tony.kay07:07:26

chaining is a bad practice in general

tony.kay07:07:37

[{:dropdown-options (om/get-query DD)} ({:subdata (om/get-query Union)} {:current-selection :a})]

tony.kay07:07:49

which you could queue up with two calls (on one event) to load

cjmurphy07:07:58

There are other ways. Good to think...

tony.kay07:07:02

and use :params to send the current selection

tony.kay07:07:37

either the server knows the complete answer, or you know a fact you can tell the server. There is no need for chaining in these cases 🙂

tony.kay07:07:14

(and two calls to load in the same event handler will be sent together as one request)

tony.kay07:07:57

(load this :dropdown-options DD) (load this :subdata Union {:params {:current :a}})

cjmurphy07:07:11

Yeah the first mutation is choosing the one to be selected as current in the drop down, but that is the wrong place for that choice, I think...

tony.kay07:07:02

It’s tough to un-learn the habits of js 🙂

cjmurphy07:07:51

Well especially when you never were a js programmer haha!

tony.kay07:07:21

You’re doing a thing like “choose your make of car”, then “choose a model”, where model gets loaded after they choose a make

cjmurphy07:07:18

Yes - one dd to another dd. My first is types of account - Expense, Income etc

cjmurphy07:07:35

And second is then all the expenses say.

tony.kay07:07:00

you prob don’t even need a union then

cjmurphy07:07:31

Are you saying I should have all the models for all the cars already loaded?

tony.kay07:07:48

1. the initial setup (e.g. a load of the first dd content) 2. The user interacts with the dd. This triggers (transact! this '[(choose-first {:val v})]) 3. The choose-first defmutation: 3a. Threads state through a function that updates the dropdown they interacted with 3b. continues the thread through a load-action that can send the proper query and param based on v 3c. Has a remote of (remote-load env)

tony.kay07:07:33

When the load completes, the second dd is now populated

tony.kay07:07:56

(assuming you targeted the load-action result to the dd state, or otherwise got it there with a post-mutation)

cjmurphy07:07:18

Hmm - problem is I need to load the second lot before any interaction.

tony.kay07:07:28

but you don’t know which to load

tony.kay07:07:36

until they select the first dd content

cjmurphy07:07:47

Yes - the default one that the first mutation is choosing.

tony.kay07:07:07

how is the default chosen?

tony.kay07:07:29

you must have known it at step (1), so you can do it during that load

tony.kay07:07:48

so yes, you’d preload the “models” for the known “default” make

cjmurphy07:07:53

By default "Expenses" say. Kind of arbitarily - perhaps the most popular choice in my mind, so user has to do less actions.

tony.kay07:07:16

yeah, but you’re loading data they may not want to see at all, which is extra overhead for your user and your server

cjmurphy07:07:20

I've got no "No choice" in the drop down.

tony.kay07:07:21

bad for multiuser performance

tony.kay07:07:51

but whatever…you can do it either way, as I just described…just move the default choice load to step (1)

cjmurphy07:07:40

But they are always going to go to the next step.

tony.kay07:07:05

I don’t understand “but”. What’s missing?

tony.kay07:07:14

the next step is as I described above

tony.kay07:07:26

event triggers dd update and next load

cjmurphy07:07:37

Was talking about perf, I didn't get the issue there.

cjmurphy07:07:42

If I make the first choice explicit at the client level (not defmutation level) then I think my design becomes better.

tony.kay07:07:52

say you show me expenses. You just loaded data from the server. That just consumed CPU and disk IO. Say you have 10000 users. They all go to that screen. You just did 10000 bits of CPU and IO. Now let’s say 20% of them didn’t want that. You just blew 2000 users worth of interaction time…slowing down the other 8000 as well.

cjmurphy07:07:16

ie "Expenses" - no reason that should be down at defmutation level - then my problem goes away.

cjmurphy07:07:19

They will all want it in my use case. They are creating a rule that they must create. So the 20% you mention is really 100%.

tony.kay07:07:43

oh, then you already know everything you need at step 1

cjmurphy07:07:28

Yes. But I needed your help to get a better design. So thanks for taking the time. Working solo here...

tony.kay07:07:38

NP. Glad it helped

tony.kay07:07:05

My experience with Untangled so far has been: If it’s hard, you’re doing it wrong or missing a concept.

tony.kay07:07:32

So, when I run into something that seems difficult, I try to remind myself of that.

tony.kay07:07:06

so far, I have yet to find anything that didn’t work out 🙏

tony.kay07:07:16

:knock-on-wood:

tony.kay07:07:01

The “chaining” temptation is tricky: I loaded A, now A told me something that makes me want B. The thing is, unless you’re loading from more than one server, the server knew about what was in A when it sent it to you, meaning you could have also asked for B in a generic sort of way and had it handled at the same time.

tony.kay07:07:29

There’s nothing technically impossible about using transact! within a mutation, other than it leads to a design where you could conceivably end up with recursive-looking transaction loops. So, poor design. If you had to do a two-server chained load, I think you’d probably be stuck with it, though. I just strongly encourage not doing it in most cases. Hm. actually, I take that back. There’s no way to get the timing right. You’d probably have to install a merge handler and trigger the second one from there. Or use a post mutation to trigger a transact! against app. Post mutations are not allowed to have remote sides (they’re ignored).

tony.kay07:07:53

If possible, I’d probably design server A to talk to server B for me, and avoid the whole client scenario 😜

wilkerlucio18:07:51

hello people, I would like to announce a new little open-source project I built with Untangled, it's a Chrome extension to represent an Youtube Queue based your emails, I made a post about it, I hope you can enjoy it 🙂 https://medium.com/@wilkerlucio/using-gmail-messages-to-track-youtube-channels-ef35308a0ceb