Fork me on GitHub
#fulcro
<
2018-01-13
>
mss01:01:46

are there any resources on how to debug data not getting normalized properly post fetch from a remote? pesky return from a union query isn’t getting normalized and I’d like to figure out why. looking for something a little bit lower level than tree->db if that exists

mss02:01:52

fwiw, the components I’m working with look something like the following:

(defsc Assertion [this props]
  {:query [:db/id
           :assertion/text]
   :ident [:assertions/by-id :db/id]})


(defsc Event [this props]
  {:query [:db/id
           :event/text]
   :ident [:events/by-id :db/id]})

(defsc TestStep [this props]
  {:query (fn []
            {:assertions/by-id (prim/get-query Assertion)
             :events/by-id     (prim/get-query Event)})
   :ident (fn []
            (cond
              (and (contains? props :event/text)
                   (not= (:event/text props) ::prim/not-found))
              [:events/by-id (:db/id props)]
              (and (contains? props :assertion/text)
                   (not= (:assertion/text props) ::prim/not-found))
              [:assertions/by-id (:db/id props)]))})

(defsc Test [this props]
  {:query [:db/id
           :test/name
           {:test/steps (prim/get-query TestStep)}]
   :ident [:test/by-id :db/id]})
and the shape of data returned by my remote looks roughly like:
[{:db/id            1
                 :test-suite/name  "My Test Suite"
                 :test-suite/tests [{:db/id      2
                                     :test/name  "First test"
                                     :test-steps [{:db/id                     3
                                                   :assertion/text "assertion text"}
                                                  {:db/id                 4
                                                   :event/text "event text"}]}]}])

mss02:01:40

I’m guessing it has something to do with the TestStep and Event/`Assertion` components declaring functionally the same idents. I don’t know if that’s actually an issue though

tony.kay02:01:37

@mss idents on all components used in the union. Query and data must match. Other than that there isn’t much to it. I realize it is a little heady because of the alternation…but if you just look at it carefully you’ll see the mismatch

tony.kay02:01:09

The merge system is very well-tested. There’s nothing lower level to see

tony.kay02:01:55

You should test your ident function against real data for one

tony.kay02:01:26

it looks reasonable to me, but you have not show the actual load you’re using compare to the data being returned, so I can be of no further help. Unless you mean to claim that Test is the proper thing to query that with

tony.kay02:01:36

because it doesn’t match

tony.kay02:01:46

:test-suite != :test

tony.kay02:01:59

so your join on Test cannot possibly match (or normalize) that data

mss02:01:11

you’re right, sorry. was trying to reduce it to the simplest possible branch of my state tree and accidentally left the test-suite part in

tony.kay02:01:32

what is your load?

mss02:01:40

if I run

(prim/tree->db Test
               {:db/id      2
                :test/name  "First test"
                :test-steps [{:db/id          3
                              :assertion/text "assertion text"}
                             {:db/id      4
                              :event/text "event text"}]})
I get:
{:db/id 2,
 :test/name "First test",
 :test-steps
 [{:db/id 3, :assertion/text "assertion text"}
  {:db/id 4, :event/text "event text"}]}

tony.kay02:01:07

well, hold on

mss02:01:45

where I would expect to see something like:

{:db/id 2,
 :test/name "First test",
 :test-steps
 [[assertions/by-id 3]
  [events/by-id 4]]}

tony.kay02:01:26

and the prop names actually match in your real code?

tony.kay02:01:52

show me (prim/get-query Test)

mss03:01:51

[:db/id
 :test/name
 {:test/steps
  {:assertions/by-id [:db/id :assertion/text],
   :events/by-id [:db/id :event/text]}}]

tony.kay03:01:13

test-steps vs test/steps

mss03:01:54

😵😵😵😵 my goodness. makes sense

mss03:01:09

also good to know that next time I see something super weird like this it’s almost certainly a typo

tony.kay03:01:23

It’s funny…I have the same kinds of trouble sometimes trusting the mechanisms internals…but it is always that simple in my experience.

tony.kay03:01:49

if you get something you don’t expect, you screwed up a query or ident or data property name. Nothing more to it 🙂

tony.kay03:01:08

but since they all have to match perfectly, it isn’t any harder to debug than careful reading

mss03:01:41

yep makes total sense. really appreciate the work you put into this, and thanks for the help

tony.kay03:01:16

sure. If you think of an idea for giving people warning on this efficiently, glad to hear it 🙂

tony.kay03:01:57

I guess we could put in some kind of dev time warning during db->tree, but it would likely be too noisy to be useful

tony.kay03:01:20

If we had schema that said what was required that might help

tony.kay03:01:27

but that all adds a lot of overhead

tony.kay03:01:42

I guess a warning in the direction of “your data contains something not in the query” would be best we could do

mss03:01:10

yeah exactly. I wonder if there’s some way to have a should-be-there type schema (the req’d keys) fall out of a query and only pay that overhead in a dev env. happy to poke around and try something if you have any ideas about the easiest way to do that

tony.kay03:01:38

I think it can be done without schema

tony.kay03:01:10

basically just a db->tree that complains if it sees something at the “current” query level in the data that isn’t queried for would probably be sufficient

tony.kay03:01:22

rather tree->db

tony.kay03:01:46

Could make two versions: one for dev mode

tony.kay03:01:42

If you don’t query for it, you don’t get it back. If you query for it, you might not get it back. But if you get something back, you should have definitely queried for it.

tony.kay03:01:03

and that check is easy to implement and would catch this specific error

tony.kay03:01:09

tree->db should probably also filter the result with the query, so stuff would disappear completely

tony.kay03:01:19

that would be a better indicator as well

tony.kay03:01:31

seeing data non-normalized fools you into thinking you got it part-way right

tony.kay03:01:40

when in fact it doesn’t match the query

tony.kay03:01:02

I you wanted to copy the code for tree->db and make a checked-db->tree ppl could use that (less efficient) one to debug, and we could also test it realtively easily since there is a reference implementation that is known to work properly.

mss03:01:25

absolutely, that makes sense. will take a crack at it over the weekend

tony.kay08:01:01

Updated documentation to include better detail on additional client options: http://book.fulcrologic.com/#_useful_reconciler_options

tony.kay08:01:23

this also discusses shared state, which can be useful in some common circumstances.

levitanong18:01:10

hi all! I’m trying to reconcile using df/load-action or any other *-action and avoiding circular dependencies. df/load-action lives in the mutations namespace, but to use it, I either have to require some namespace with a component Foo, or I have to declare a new component Foo within the mutations namespace. The latter seems wasteful, but the former will inevitably result in a circular dependency. What am I missing?

cjmurphy19:01:32

The thing I did to get round this was to put components into state on application startup load.

cjmurphy19:01:54

In the :started-callback.

cjmurphy19:01:16

I have a mutation (only called once, in :started-callback) that does this: (swap! state assoc :root/components components).

cjmurphy19:01:48

The components mutation parameter might look like: {:organisation ~bookkeeping/Organisation}

cjmurphy19:01:30

Then whenever a component is needed: (get-in st [:root/components :organisation])

cjmurphy19:01:25

Another possibility is to pass in the components you need as parameters. Even post-mutations take parameters. I think they didn't in the past, which is why I resorted to putting components in state. But having them in state (put there only at application startup) kind of works as components are static things.

tony.kay20:01:25

@cjmurphy not a good idea to put components in app state. They are not serializable.

cjmurphy20:01:31

But they don't ever go over the wire where I use them - just put them in a link.

tony.kay20:01:05

until you decide you'd like to use history for something like a support viewer.

tony.kay20:01:25

so, couple of thoughts:

cjmurphy20:01:48

But no one would care about history for those components which are in a link. Just saying 🙂

cjmurphy20:01:31

Right I get it - the state would be no good in the target environment - but if started app from beginning they would be overwritten.

tony.kay20:01:42

@levitanong Mutations are just symbols. You don't need to require them in your UI

tony.kay20:01:01

there is no need for circular referencing..they'll work. The only thing you lose is ns aliasing

levitanong20:01:07

Ah, right! Thanks.

tony.kay20:01:19

@cjmurphy If they are there at all, then serialization will crash

tony.kay20:01:34

The other thing you could do that is OK is use :shared

tony.kay20:01:38

that I documented last night

cjmurphy20:01:44

oh right, crash 😝

tony.kay20:01:36

but ultimately, calling a mutation does not require you to require their ns in the UI. Ever. The ns aliasing is just a convenience.

cjsauer22:01:12

Just became aware of Fulcro's book release! http://book.fulcrologic.com Congrats @tony.kay, and a huge thank you for open sourcing your inspiring work!