Fork me on GitHub
#om
<
2016-03-02
>
george.w.singer00:03:20

Is it a best practice (or even a requirement?) that om next mutations be made relative to an ident?

george.w.singer00:03:11

For example in the Components, Identity, and Normalization tutorial that is the way things were done:

clojure
(defmethod mutate 'points/decrement
  [{:keys [state]} _ {:keys [name]}]
  {:action
   (fn []
     (swap! state update-in
       [:person/by-name name :points]
       #(let [n (dec %)] (if (neg? n) 0 n))))})
i.e: the app-state was modified by accessing the data through the [:person/by-name name] ident.

tawus00:03:44

I am trying to use link in a query [[:some/link _]] and in the render function I get (om/props this) = {[:some/link _] right/data}. Shouldn’t the key be :some/link instead of [:some/link _]. What am I doing wrong here ?

bnoguchi00:03:49

@tawus: The key is supposed to be what you’re seeing. You can access the value as (props [:some/link _])

tawus00:03:01

https://github.com/omcljs/om/wiki/Thinking-With-Links!#embracing-links, :current-user is accessed using :current-user not [:current-user _] and that is what is causing the confusion for me.

hueyp00:03:29

it might depend on if you use a join … I know with a join there were some inconsistencies that got fixed on master but not in alpha-30 yet

hueyp00:03:39

but I don’t remember if that was one of them

bnoguchi00:03:00

@tawus: Not sure. I’ve just been going by what I see at runtime.

dnolen01:03:10

@anmonteiro: would like to see minimal example of that

cjmurphy01:03:28

@george.w.singer: To me just looks like decrementing :points in the tables part of state that is in default db format. i.e. directly changing the actual data. You state does not have to be in default db format. But I think it makes life easier if it is!

george.w.singer01:03:56

But isn't it changing the automatically generated normalized data that om next produces via Ident declarations made in components?

george.w.singer01:03:20

i.e., it's not changing the actual data but generated data

cjmurphy01:03:41

Changes are made in mutations that quite often come from components via transact calls.

cjmurphy01:03:45

I said 'actual data' to mean not some reference - the tables part of default db format is where all the actual data lives.

george.w.singer01:03:08

Another related question (that might clear up some of my overall confusion here): I'm assuming that, in the same way om next reads are made relative to the root query/app-state, all om next mutations are made relative to the root app-state as well.

cjmurphy01:03:28

The 'by-id' or 'by-name' (in this case) area of your data.

george.w.singer01:03:58

RE your response: so om always creates a table (behind the scenes) out of the data you give it?

cjmurphy01:03:11

Well default-db-format is flat - so assumptions can be made.

george.w.singer01:03:21

I thought you had to specifically use Idents for that to occur

cjmurphy01:03:08

If :normalized true. For what to occur?

cjmurphy01:03:48

Queries are a different story - you just use the query grammar.

cjmurphy01:03:00

Idents are part of default db format, but update-in [:person/by-name name :points] is just relying on the shape of the default db format data.

george.w.singer01:03:43

The :person/by-name key in the app-state was generated by om next via Ident declarations

george.w.singer01:03:48

In that tutorial

george.w.singer01:03:10

If those Ident declarations weren't made, that key wouldn't be in the database

cjmurphy01:03:14

Yes - you need your components to have ident methods

george.w.singer01:03:58

when you crate a mutate method for your parser

george.w.singer01:03:11

and you are mutating the :state passed in via env

george.w.singer01:03:29

that state is always going to be reconciler's app-state, right?

george.w.singer01:03:34

the full app-state

george.w.singer01:03:36

not some portion of it

cjmurphy01:03:39

I've never needed to do that.

cjmurphy01:03:12

I don't know.

george.w.singer01:03:45

I mean, that appears to be what's going on right here, for example: (defmethod mutate 'points/increment [{:keys [state]} _ {:keys [name]}] {:action (fn [] (swap! state update-in [:person/by-name name :points] inc))})

george.w.singer01:03:55

(defmethod mutate 'points/increment
  [{:keys [state]} _ {:keys [name]}]
  {:action
   (fn []
     (swap! state update-in
       [:person/by-name name :points]
       inc))})

george.w.singer01:03:13

in any event, thanks for your help

cjmurphy01:03:55

To me the above is just changing your state, and there is only one state. There's no env involved.

george.w.singer01:03:22

env is first argument passed into the mutate method

cjmurphy01:03:33

Oh I see what you are saying.

george.w.singer01:03:35

which has a key called :state

george.w.singer01:03:49

the value of which I'm assuming is always just the app-state

cjmurphy01:03:49

I just assume it is the whole state coming in.

george.w.singer01:03:05

That's what I'm confused about lol

george.w.singer01:03:16

I think you're/we're right I was just getting some confirmation simple_smile

cjmurphy01:03:40

Took me a while to work things out, like when using get it is to the refs part of default db format, and when using get-in it is to the tables part.

george.w.singer01:03:21

What do you mean the refs part of the db?

cjmurphy01:03:02

That's where you have vectors of idents, each under a key.

cjmurphy01:03:24

Actually just one ident is okay too, but usually they are vectors of idents.

cjmurphy01:03:58

Example:

:list/one
 [[:person/by-name "John"]
  [:person/by-name "Mary"]
  [:person/by-name "Bob"]]

cjmurphy01:03:00

'ref entries' and 'table entries'. (map entries).

cjmurphy01:03:09

Notice you can use a ref entry to easily lookup a table entry using get-in.

tawus02:03:13

Can you take advantage of normalization once we implement routing using set-query! ?

tony.kay02:03:11

@dnolen: Any chance you can cut a release after merging PR 640? I have approval to open-source my stuff, and I want to cut a release based on the latest om alpha with that fix.

cjmurphy03:03:08

@tawus: I haven't needed to use set-query!. But I think the ident method is the important thing. As long as your new query includes the :id (for by-id ident) then things should work as before. (I'm sprouting this info - expecting to be wrong at any moment).

tawus03:03:59

I think using set-query! to set Root’s query is messing up normalization for me. Now I am trying to do away with set-query! and see how things work.

cjmurphy03:03:09

You can use something to check whether it is being messed up or not: https://github.com/chrismurrph/default-db-format. That's a plug. But I wrote it to help myself because was often doing bad mutations, and rely on it now.

cjmurphy03:03:00

Hmm - root's query - that's what it is all about - normalization happens from root's query (or seems to me).

tawus03:03:45

Yes, I am trying to normalize at merge now. Let me see how that goes 😞

tmorten03:03:07

@anmonteiro: I have run into this same situation as well using routing and idents with my project...

tmorten03:03:36

There is no way to get at the ident in props

tmorten03:03:28

@anmonteiro: maybe it isn't so "edge" case?

tawus03:03:53

@tmorten: Are you facing the same issue as I am ?

tmorten03:03:21

@tawus: I don't believe I have an issue with normalization using set-query!

tmorten03:03:53

I do write a parser read method for each ident lookup I need to do..

tmorten03:03:31

Let me dig up an example...

tmorten03:03:28

My biggest struggle when using normalization is trying to keep it in my mind at once simple_smile. My mind likes to think in trees

tmorten03:03:01

then my query looks like so:

tmorten03:03:41

Perhaps you are already doing this...

tawus03:03:17

No, I have never used query-root from env. What is it ?

tmorten03:03:51

Essentially it is the "root" of your query. In this example it would be [:transaction/by-id <id>]

tawus03:03:13

Ah. I am getting a few ideas now simple_smile

tmorten03:03:46

sorry that is actually what the query root looks like

tmorten03:03:12

There may be a better way to do this...just how I am doing it now

tawus03:03:22

Here is my problem. I am routing UserList (composed of UserItem having ident user/by-id) and UserDetails. When the routing starts with UserDetails (/user/1235), I can’t use [user/by-id 1235].

tmorten03:03:14

I have set id to nil in the params method...in which case the parser will return nil as well. When the param finally comes in using set-query! it will all load up. You can do some fancy ui "loading" display until you have the real parameter

tmorten03:03:30

You'll need to take advantage of the life cycle methods to do handle your particular (= id nil) scenario

tawus03:03:50

That is what I am doing too. I think the above read method using query-root for idents might be something that solves my problem.

tmorten03:03:19

Yes, I will tell you the default read method won't do it for you because you'll need more than just the keyword :user/by-id to reach into the map...you'll also need the id. That is what query-root gives you.

tmorten03:03:09

Essentially this is what you'll get (get-in @app-state [:user/by-id 1234])

tmorten03:03:35

where [:user/by-id 1234] is the query root given to your parser method

tawus03:03:22

For me the db is not normalized, can’t be, because of routing.

tmorten03:03:57

Are you giving the reconciler an atom or no atom?

tawus03:03:31

not an atom but imagine set-query! starting with Login, so the root query will not have UserList’s query available at the time of normalization. Once I set-query! to UserList’s, data is fetched for :user/list but normalization doesn’t happen.

tmorten03:03:59

It may or may not be normalized depending upon how you are returning the state from your read method. IF you return it using db->tree it will be converted to tree

tmorten03:03:17

like I did in the example above.

tmorten03:03:42

in my props, the data would be tree since my value key is returning the db->tree conversion

tmorten03:03:04

You can deref your reconciler to make sure your actual app-state is being normalized...i.e. @reconciler

tmorten03:03:20

I usually pprint it to my browser console

tmorten03:03:28

(pp/pprint @reconciler)

tawus03:03:36

Yes, I think I am getting somewhere with this new way.

tawus03:03:53

I’ll try it out and may be let you know how it played out. Thanks for all the help. Really appreciate it!

tmorten03:03:39

No worries. Good luck!

artemyarulin06:03:27

Hm, can any body help. I cannot get query-root information in my send function:

(defn read [{:keys [query data state target parser ast] :as env} key params]
  (case key
    :current {(or target :value)
              (assoc ast :query (parser (assoc env :data (get-in @state (get @state key))) query target))}
    :content (if-let [c (get data key)]
               {:value c}
               {:remote (assoc ast :query-root true)})))

(defn sender [remotes cb]
  (println "Equal:"
           (:query (om/process-roots (:remote remotes)))
           (:remote remotes)))
Looks like I’m marking it right, but still process-root in send function returns same query as like without it

jplaza06:03:44

Newbie questions here: what does the AST represent and why it is returned as the remote value in the remote sync tutorial?

tawus07:03:49

@artemyarulin: setting query-root only works with joins (not sure about unions) but it surely doesn’t work with properties.

george.w.singer07:03:21

When you write a mutate function, it is customary to to place the keys that will need to be re-read by om via the :value key (inside the return value of the mutate function).

george.w.singer07:03:27

Yet it is ALSO customary to flag these keys when calling a mutation query (i.e., when making a query like [(my-mutation {:mutation params} :keys :to-be :re-read].

george.w.singer07:03:35

Why the need to double-book these keys which you are flagging to om to re-render?

cjmurphy07:03:39

:value is for documentation because many users of the mutation - so you can see how to do it. Pinned item for this as well.

bnoguchi07:03:28

Anyone encounter this om next error before?

Uncaught #error {:message "No queries exist for component path (app.components/Router app.components/RegisterRoute)", :data {:type :om.next/no-queries}}

george.w.singer08:03:46

So the {...:value :keys :to :be :reused ...} within the return value of a mutate function has no effect on the program?

george.w.singer08:03:08

It's purely for code readability?

artemyarulin08:03:56

@tawus: Thank you. Hm, do you have any example of using query-root? Looks like I don’t understand it fully

cjmurphy08:03:51

Its explained in the pinned item which is a picture .png << @george.w.singer . Answer is yes, just for readability.

george.w.singer08:03:03

@cjmurphy: didn't know about pinned items in Slack. Thanks for re-routing me. And yes: the answer to my question was sitting in one of those pinned items

andrewboltachev09:03:02

Hi. Does Ambly only support iOS devices? If yes, what is used on Android for React Native?

george.w.singer09:03:37

(defmethod read :customers/by-id [env k params]
  (let [st @(:state env)]
    (if-let [[_ v] (find st k)]
      {:value v :remote (:ast env)}
      {:value :not-found})))
Here's a snippet showing a read function. Hone in the :remote (:ast env) part. Here we're telling the read function to send the query (:ast env) to the remote :remote. Why do we have to send queries in the form of abstract syntax trees to remotes? I tried sending a [:literal :query] but when doing so, the corresponding send function isn't even called!

artemyarulin09:03:40

@george.w.singer: I guess the reason is that you can return AST in many places and later on it will be composed together and then transferred to send function. If you return [:literal :query] in one place, then another literal in another - om wouldn't know how to compose this staff and create one query

george.w.singer09:03:35

According to the docs, a reconciler's send function takes two arguments: a map with the query and remote information, and a callback. For example, here is a sample send function taken from a blog post:

(defn send [m cb]
  (let [xhr (new js/XMLHttpRequest)]
    (.open xhr "POST" "/props")
    (.setRequestHeader xhr "Content-Type" "application/transit+json")
    (.setRequestHeader xhr "Accept" "application/transit+json")
    (.addEventListener
      xhr "load"
      (fn [evt]
        (let [response (t/read (om/reader)
                               (.. evt -currentTarget -responseText))]
          (cb response))))
    (.send xhr (t/write (om/writer) (:remote m)))))
My question though is where does that callback come from (i.e., who defines it -- the server or the client?) What's its typical use case?

george.w.singer09:03:08

By "callback" I'm referring to the second argument of the send function (in this case: cb).

rauh10:03:11

@george.w.singer: IIRC, the callback cb is just om/merge! with the reconciler already passed for you.

george.w.singer10:03:19

Ok. So weird tho -- I can't find any documentation on this cb.

anmonteiro13:03:22

@tawus: links are only transformed to keys when you use db->tree

tawus13:03:46

@anmonteiro: My problem was that component properties were indexed with [:user 23746] rather than :user for the query [[:user 23746]]. That turned out to be a normalization issue.

anmonteiro13:03:22

@tawus: still, if you query for '[:my-prop _] and don't use db->tree, you'll end up with the link in the component's props

tawus13:03:46

Thanks @anmonteiro, I have passed that stage now but I am still struggling with a simple CRUD application with routing support. I am now waiting for @tony.kay’s read mutation approach. Hope that makes things easier.

anmonteiro14:03:54

@dnolen: condensed minimal devcards example and the fix in this PR: https://github.com/omcljs/om/pull/642

anmonteiro14:03:10

the fix works for the devcards example as well as for the project I was having this issue in

anmonteiro14:03:30

@dnolen: I'm sure you've thought about it already, but I'd suggest that the release would not include the dynamic query stuff

anmonteiro14:03:47

We need to probably give it a spin for some time before incorporating it in a release

anmonteiro14:03:52

I'm sure there'll be edge cases

tmorten16:03:56

@anmonteiro: thanks for the fix!

sebluy18:03:18

Is there a way to do a local mutation when a remote response comes back? I'm trying to make a component that displays the number of pending remote queries.

hueyp18:03:32

you might be able to just merge that into the callback

hueyp18:03:52

keep the state in your send function, and then merge that state into the response on callbacks ?

sebluy19:03:27

Hmm, I tried just swap!ing the state which didn't cause a re-render, but I'll see if I can get something to work with merge.

hueyp19:03:08

swap! should cause a re-render

hueyp19:03:21

it has in my experience (e.g. swapping in a history app state)

iwankaramazow19:03:34

@sebluy: After the transaction, Om implicitly updates the initiating component based on its query. However, if keys outside the component are affected, they need to be added to the transact! query expression explicitly.

sebluy19:03:53

Alright, it seems to be working when I transact! on the reconciler to increment the count in my send function, and then merge in a decremented count on the response. Kind of hackish but whatever.

iwankaramazow19:03:12

incrementing/decrementing a counter shouldn't feel 'hackish' 😛

sebluy19:03:12

That's probably just because I got a lot more to learn about om/next.

iwankaramazow19:03:48

@jlongster: how's the performance of virtualized list with set-query?

jlongster19:03:17

if I write #js {:foo 1} with transit and read it back out I get {"foo" 1}. is there any chance I can customize it to read back out keywordized clojure maps?

iwankaramazow19:03:51

I vaguely remember something about :keywordize true or something similar

jlongster19:03:11

@iwankaramazow: it works well with rerendering itself, if that's what you mean. I've have good experiences with the component. I'm currently having other perf problems unrelated to it (working with lots of data)

jlongster19:03:06

js->clj takes keywordize as an option

jlongster19:03:14

don't see it anywhere in transit though

dnolen19:03:05

@jlongster: not supported, also IMHO keywordize is not a good idea

dnolen19:03:08

nor is js->clj

dnolen19:03:15

if you can avoid all of these things - you should

dnolen19:03:49

also string key destructuring is a thing, so that might work for you

dnolen19:03:07

(let [{:strs [foo]} {“foo” 1}] foo)

jlongster19:03:02

@dnolen: I'm trying to avoid extraneous work on a big set of data. I'm getting JS objects back from a SQL database, and trying to pump them out the to UI as quickly as possible. I've optimized the server to just write the JS objects directly, but the frontend gets {"foo" 1} now which doesn't work with Om Next.

jlongster19:03:11

oh I didn't know you could destructure strings, thanks!

jlongster19:03:37

I may just have to implement custom everything to handle this data (normalize it, etc). Really not sure the best way to handle it yet.

iwankaramazow20:03:34

Not sure if there is a good way

iwankaramazow20:03:55

I'm currently stressing myself with 'only'30000 items

jlongster20:03:26

Oh yeah, my data isn't "big data" at all, I'm testing with ~50000 items as well

iwankaramazow20:03:46

there's some 'lag' here, i.e. a user on a monday morning my get cranky from it

jlongster20:03:20

but my app is so centered around this data (basically a big spreadsheet to interact with it) I'm not exactly sure anymore if Om Next is the right fit for it. Will still play around with it though.

iwankaramazow20:03:58

Are there other technologies that qualify for this use case?

jlongster20:03:54

nothing much better, really, at some point you have to implement what you want yourself. I'll still probably use Om Next for general UI, but might just implement custom stuff for the core part of it

jlongster20:03:30

because I don't really need normalization etc for this core part (it's one big list of homogeneous data, somewhat easy to keep track of)

dnolen20:03:35

@jlongster: also worth look at the source of transit-cljs

dnolen20:03:50

unwilling to add an explicit keywordize

dnolen20:03:00

but it should be possible anyway

dnolen20:03:28

see :mapBuilder option by examining cognitect.transit/reader

jlongster20:03:34

oh sure, I was wondering if I can override the custom "map" handler and do it myself? still, not sure I need to do that anymore if I manage this data all myself

jlongster20:03:37

oh sweet, thanks

jlongster20:03:25

@dnolen: hm I don't see :mapBuilder anywhere in here? https://github.com/cognitect/transit-clj/blob/master/src/cognitect/transit.clj (I see the map-builder function)

dnolen20:03:23

@jlongster: I was assuming the backend was JavaScript

dnolen20:03:31

front end ClojureScript or something like that

jlongster20:03:36

ooh that explains a lot

jlongster20:03:46

backend is actually CLJS as well simple_smile

dnolen20:03:17

@jlongster: so I guess the DB thing you’re using returns JSON so you can’t control that part

dnolen20:03:45

if that’s the case then yeah my recommendation above holds

jlongster20:03:46

yeah, node-sqlite3

iwankaramazow20:03:00

do you filter server side ?

jlongster20:03:19

@iwankaramazow: yes, although that may change, I'm considering loading everything in the client up front but I'm not sure yet.

peeja22:03:33

Is there a canonical way to do mixins in Om Next?