Clojurians
#om
<
2016-02-28
>

This page is not created by, affiliated with, or supported by Slack Technologies, Inc.

artemyarulin06:02:54

Going through Tony Kay tutorial, as an exercise: Is there standard syntax for JOIN everything? Like with this data:

{:people/by-id {1 {:person/name "Sally" :person/age 33}
                2 {:person/name "Jesse" :person/age 43}
                3 {:person/name "Bo" :person/age 13}}}
how can I get all people names? I know I can do smth like [{:people/by-id [{1 [:person/name]} {2 [:person/name]} {3 [:person/name]}]}], but imagine that I don’t know all id’s? Can I somehow make a JOIN by-id to smth like * and then join to name? Or can I do two queries, one for all-ids and another for names?

artemyarulin06:02:57

It’s a silly example, but I’m just trying to fully understand om queries here

hueyp06:02:34

you can make a key ‘people’ or ‘all-people’ or whatever, that grabs the vals of ‘people/by-id’ … [{:people [:name]}]

hueyp06:02:12

but it isn’t real a query syntax as much as just deciding if you want to expose that data via a key

artemyarulin06:02:25

hm, but then it means that I would have to manage all-people manually and add/delete items there

hueyp06:02:20

sometimes you want that, but if you really want it to be ‘everything in people/by-id’ then you just grab those vals

hueyp06:02:43

(db->tree query (-> db :people/by-id vals) db)) or something

artemyarulin06:02:19

yeah, you right - if I need those values the right way would be to manage it

hueyp06:02:51

I think the examples show like {:todos [[:todo/by-id 1] [:todo/by-id 2]] etc

hueyp06:02:05

so the todos key is the managed list and then you have the map for by-id lookups

artemyarulin06:02:33

yeah-yeah, I know this basic staff, thanks

artemyarulin07:02:57

Another question if I may. Getting into Unions queries with DB like:

{:panels        [[:panelA 1] [:panelB 1] [:panelC 1]]
 :panelA        {1 {:boo 42}}
 :panelB        {1 {:goo 8}}
 :panelC        {1 {:sticky true}}
 :current-panel [:panelA 1]}
both those queries returns the same result:
>[{:panels [:boo :goo :sticky]}]
>[{:panels {:panelC [:sticky], :panelA [:boo], :panelB [:goo]}}]
{:panels [{:boo 42} {:goo 8} {:sticky true}]}
what the point of union then?

artemyarulin07:02:35

ah, sorry - got it. With Unions I can specify custom queries for each group, which may be much more complex than simple props

thiagofm12:02:01

I found a problem. I have a setInterval with some mutation, as it's a setInterval, I set it up on componentWillMount and clear on componentWillUnmount. All this works perfectly. In the mutation I search some data and update a key according to the data I get. All this using Datascript. But after talking here. I found out that this approach isn't good, as it doesn't have any input and it always returns different output, "side effects" as we call it. But if I try to refactor the code in a way that I query in the component level the data I need to use to update some key inside the mutation(so it becomes side-effect free), the component stops updating. The reason seems that when I set the "setInterval" in componentWillMount, the data passed there will always be the data of when the component was mounted. A very easy mental example of this is, let's say you had a counter that increases with time, with a setInterval of 1000 that adds a random value between 1 to 9 there(a side-effect, so it this number to be passed to the mutation), if you build this "setInterval" inside componentWillMount, it will only work for the first second, all the subsequent will be trying to enter the same data. How to circumvent that problem? Is it possible to have my mutation side-effect free in this case?

thiagofm13:02:06

I hope I was clear enough

anmonteiro13:02:29

@thiagofm: I'm pretty sure you're confusing a lot of concepts there

anmonteiro13:02:23

Mutations are inherently not side-effect free, in the sense that you want your counter to keep increasing, i.e. different calls to the same mutation don't return the same result

anmonteiro13:02:06

what needs to be side-effect free is the return value of the parser. This is why we return an :action thunk instead of the side-effects

anmonteiro13:02:16

in sum, there's absolutely no problem of having side-effects in your mutation, as long as they're encapsulated in the :action thunk (function with no arguments, e.g. (fn [] (fire-missiles!)))

thiagofm13:02:09

Okay then, so it's right. It's just that let's say I didn't have the problem with the "componentWillMount", it would be possible to have a function that with the same arguments always return the same result(or to the :action thunk)

thiagofm13:02:09

I still don't get much concepts, but the more I wrestle with setTimeout/setInterval, it looks like if om provided some sort of abstraction for those things it would make this sort of stuff possible

anmonteiro13:02:01

@thiagofm: well, Om does render on requestAnimationFrame, so it's perfectly possible to create an animation loop without setTimeout and setInterval

anmonteiro13:02:02

providing abstractions like those are completely out of scope of what Om intends to be

anmonteiro13:02:17

those fall completely on the user side

thiagofm13:02:29

That is fine, I mean, I could write my own thing that would abstract that for me. I'm pretty sure as om next evolves people will come up with solutions for problems you could have when trying to write something that does X or Y in om next.

anmonteiro13:02:04

I'm sure that'll be the case

lsnape17:02:58

Hi, wonder if anyone can help: I have an om next send fn that triggers an SSE event stream connection. The callback fn is called every time an event is made.

lsnape17:02:10

(go-loop [callback-fn (<! ch)]
  (-> (new js/EventSource "/stream")
      (.addEventListener "message"
                         (fn [event]
                           (let [entry (read-string (.-data event))]
                             (callback-fn {:entries [entry]})))))
  (recur (<! ch)))

lsnape17:02:17

I want entry to be appended to entries in the local app state, but at the moment it is just replacing it each time with the latest entry.

lsnape17:02:13

the reconciler doc string for the send function seems to suggest that it supports this behaviour

sander17:02:59

@lsnape: seems like you can supply custom merge functions to reconciler https://github.com/omcljs/om/blob/master/src/main/om/next.cljs#L1767

iwankaramazow17:02:15

Try this one: (defn custom-merge-tree [a b] (if (map? a) (merge-with into a b) b))

iwankaramazow17:02:29

plug that function as :merge-tree

iwankaramazow17:02:33

on your reconciler

lsnape17:02:28

@iwankaramazow: ok will do. Was having trouble finding the option to pass in!

lsnape18:02:37

@iwankaramazow: @sander great that seems to be having the desired effect. Thanks simple_smile

thiagofm18:02:07

Can't seem to be able to make the example in https://github.com/omcljs/om/wiki/Remote-Synchronization-Tutorial run 😞

thiagofm18:02:28

@iwankaramazow: are you going to update https://awkay.github.io/om-tutorial/#!/om_tutorial.G_Remote_Fetch anytime soon?

iwankaramazow19:02:29

@thiagofm: you should ask @tony.kay, didn't write it need some extra help?

tony.kay20:02:23

@thiagofm: A lot going on for me right now. I’ll try to make time to work on some of the unfinished bits of the tutorial in the next month or so.

jamesmintram20:02:37

So another question for the channel

(def init-data
  {
    :list/users {:selected-user [{:ID 0}]
                 :users [{:ID 0 :userName "Jimmy"}
                         {:ID 1 :userName "Robby"}
                         {:ID 2 :userName "Bobby"}]}})
Again - with this data set - selected-user is local only and users is remote data. The data I want to retrieve/update is the list of users. What is the best approach to achieve this?

tony.kay20:02:12

@jamesmintram: That is a really broad question in the context of Om, so you should first try to understand all the core building blocks via the tutorials. Om does not have a model where you “retrieve/update” things like you might when using AJAX and jQuery. It is a wholesale different approach. The basic answer (if you know the blocks) will involve: run a transact! that mutates something and specifies “remote true” as part of the mutation (that would get you update behavior, and even lazy loading). Initial reads could look to see if the state is missing, and indicate “remote true” for processing the UI query.

tony.kay20:02:27

In our applications, we’ve dropped doing remote queries at all, and do all loads via “lazy loading” that is done via transact!…a generalization we’re quite happy with.

jamesmintram20:02:49

@tony.kay: Yeah, the question was a little broad. I currently having it working with :list/users. The thing I am trying to work out, is triggering a read for a nested item (in this case :list/users)

tony.kay20:02:36

“triggering” almost always means: transact! a marker into the state, then during the next call of the parser (which is triggered by that), use that marker to decide what needs to remote

jamesmintram20:02:26

OK - but I have seen Om next apps laid out, leads to reads at the “root” of the data -

tony.kay20:02:48

Yes, the queries are built from the root

tony.kay20:02:51

see process-roots

jamesmintram20:02:45

OK thanks. Part of me was thinking, that in the :list/users read function, I would call another function which would deal with :users (Making this stuff composable)

iwankaramazow20:02:15

you can work with the parser recursively, if that's what you're after

tony.kay20:02:30

The problem we ran into was more interesting: our UI queries are frequently different from what we actually want to ask the server. So, we hacked into the send and just ignore the “remote read” support of Om altogether. Our transacts specify what we want to read, and then we post-process that into the app state on response.

jamesmintram20:02:59

@iwankaramazow: That sounds like what I am after simple_smile - any examples of this?

tony.kay20:02:07

The UI queries are still invaluable for the UI, we just didn’t find them as useful for remote interactions.

iwankaramazow20:02:36

@jamesmintram: @tony.kay 's tutorial has all you're looking for 😄

tony.kay20:02:42

ironically simple_smile

iwankaramazow20:02:04

@tony.kay: if I understand correct, you don't use process-roots any more?

jamesmintram20:02:05

Haha, I have been reading those - but haven’t come across that yet simple_smile

tony.kay20:02:24

@iwankaramazow: correct

tony.kay20:02:04

@jamesmintram: The tutorial has notes on a lot of this stuff, but it isn’t fleshed out well…there is also a demo app buried in there you can run that simulates a remote. worth playing with. I think it still works simple_smile

jamesmintram20:02:23

Thanks! I am checking it out now simple_smile

jamesmintram20:02:24

Oh yeah.. I somehow always forget to checkout tests. They will probably answer all of my questions.

iwankaramazow21:02:23

@tony.kay: where does the post-processing of your "transact reads" happen?

tony.kay21:02:03

So, here’s the trick: Our load “mutation” adds markers to the top level app state that record (via :action) what to load (e.g. the query to run, post processing lambda), they also return :remote true. This causes send to run. Send, in turn, sees that it was a load mutation, and instead of sending that remotely, it looks in the app state and gathers up the markers.

tony.kay21:02:36

When the response comes back, we just merge it in, and then run any post-processing lambda specified.

tony.kay21:02:31

There’s a bit more plumbing, but that is it in a nutshell…some really cool stuff fell out, too, like load-field as a mutation that can lazy-load some nested bit of UI (like comments on an entry).

tony.kay21:02:17

(load-field this :comments) helper function that runs transact…also leads to being able to auto-render a spinner on that element in the UI

iwankaramazow21:02:45

Ah that sounds really awesome

tony.kay21:02:13

Our parser logic dropped to like 20 lines of code for apps of any size

tony.kay21:02:26

very easy to reason about

tony.kay21:02:44

I hope to show this stuff off at a Unsession at clj west in April

iwankaramazow21:02:46

💪 💪 💪

thiagofm21:02:33

@iwankaramazow sorry, thought it was you! Thanks for offering help, but I'll give it a try to learn further, so perhaps I can ask you harder questions later 😉 -- @tony.kay great! The other parts of the tutorial are looking good, certainly of big help for newcomers

jlongster21:02:50

@tony.kay: I wonder if @anmonteiro's latest work about making dynamic queries a lot more feasible helps with that problem. process-roots works pretty well for me (and supplying a list of keys that are remote, my remote read just looks for those), but I can see how changing queries over time is awkward right now.

jlongster21:02:41

theoretically your transacts just turns into set-query! calls, and I think my system would be similar then, but my app isn't extremely complex yet so maybe it won't scale like I'm thinking

iwankaramazow21:02:20

Honestly this reminds me a lot of what I was doing with Netflix's Falcor

iwankaramazow21:02:48

For example you can't fetch a 'whole' list, it was either a range where you paginated or 'overfetched' with 'nil atoms'.

iwankaramazow21:02:41

Basically the only thing you could resort to in that scenario was a 'call', i.e. Om's transact equivalent.

jlongster21:02:37

@iwankaramazow: unsurprising since a lot of om next was inspired by falcor

anmonteiro22:02:04

How are people handling Om tempids when transacting against datomic?

anmonteiro22:02:27

converting om tempids to datomic tempids?

tony.kay23:02:55

@jlongster: my ideas have little to do with changing queries over time. It has more to do with having multiple views of the data, none of which represent what you actually want to ask the server for (e.g. you might want a combination of things that exists in no specific place in the UI). @anmonteiro: Yep, that’s what we do

tony.kay23:02:37

map from omid -> datomic tempid -> transaction -> dtempid -> realid -> map from omid -> realid

tony.kay23:02:29

@jlongster: In the best case, you can load using a query that is on the UI (easy enough to get). In the worst case, you define your query via defui that have no UI (for normalization only) and use that instead.

anmonteiro23:02:35

@tony.kay: that's what I'm doing too

anmonteiro23:02:57

was wondering if there were options that didn't involve so many steps

anmonteiro23:02:08

I'll write my own helper then

tony.kay23:02:51

Yeah, it is the same every time...