Clojurians
#om
<
2016-03-19
>

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

thiagofm09:03:03

Anybody has a example of using remote sync + datascript?

thiagofm11:03:48

Any tips for wrapping your head around om next's remote sync? 😞

iwankaramazow12:03:45

@thiagofm: What's the problem ? πŸ˜›

thiagofm12:03:33

@iwankaramazow: I can't wrap my head around the tutorial in the wiki, more like my problem, haha

iwankaramazow12:03:48

@thiagofm: 1) define a send function 2) return {:remote true} in your parser 3) experiment

thiagofm12:03:05

What about the dynamic and static part?

iwankaramazow12:03:43

Ask yourself: Do I want to filter out the static & dynamic part of a query and send it off to different remotes?

iwankaramazow12:03:19

If you're starting out with remotes, start with one remote.

iwankaramazow12:03:03

In the tutorial the static and dynamic are two different remotes.

thiagofm12:03:19

I guess this is where I'm having issues, mostly with terminology. What is a static or dynamic part of a query? Is a remote an ajax request?

iwankaramazow12:03:27

:static and :dynamic are two arbitrary chosen names for two remotes.

iwankaramazow12:03:20

Example use case: You have a dashboard where all users see the same items. However you can customize the dashboard a bit, so every user sees for example another background (which he chose at some point)

iwankaramazow12:03:54

You could send of the static part of the dashboard (for every user the same) to an endpoint that is http cached for performance

iwankaramazow12:03:35

the dynamic part of the dashboard ( the customization ) gets send to another endpoint which resolves for example the background color (unique to each person)

iwankaramazow12:03:54

A remote isn't an ajax request

thiagofm12:03:04

So, what is a remote? πŸ˜›

iwankaramazow12:03:12

it's an endpoint

iwankaramazow12:03:08

A place you can send your queries to

thiagofm12:03:10

Okay, so I don't have to make the request myself, it's om next that does it?

iwankaramazow12:03:49

If you return {:keyword-for-your-remote true} in your parser, Om will queue all remote sends together and send them through the function you have to declare at :send in your reconciler

iwankaramazow12:03:03

In that function the ajax stuff happens

iwankaramazow12:03:39

example:

(defn transit-post [url]
  (fn [{:keys [remote]} cb]
    (.send XhrIo url
      (fn [e]
        (this-as this
          (cb (t/read (t/reader :json) (.getResponseText this)))))
      "POST" (t/write (t/writer :json) remote)
      #js {"Content-Type" "application/transit+json"})))

iwankaramazow12:03:46

(from Om's todomvc)

iwankaramazow12:03:09

(example of a send function)

thiagofm12:03:47

That send function just sends a request and get the response, very generic, no?

thiagofm13:03:12

Okay, now after the request is sent, where are we at?

thiagofm13:03:21

How do I work with this result?

seanirby13:03:23

thiagofm: I went through everything in this project https://github.com/swannodette/om-next-demo/tree/master/todomvc to get oriented with how the back end talks to the front end

iwankaramazow13:03:36

@thiagofm: the request arrives at the remote (your backend), you resolve the query, send the stuff back and the cb provided by Om will merge the novelty back in

thiagofm13:03:31

Merge where? As a key in datascript for example?

iwankaramazow13:03:15

Haven't used datascript, you might need to declare your own merge

thiagofm13:03:10

For example, in the todomvc

thiagofm13:03:12

(defmethod mutate 'todo/update [{:keys [state ref]} _ new-props] {:remote true :action ;; OPTIMISTIC UPDATE (fn [] (swap! state update-in ref merge new-props)) })

thiagofm13:03:10

It just updates the state with the new props, the remote true will make sure that the send function is invoked

thiagofm13:03:46

But is the send function is invoked with what? How does it knows the endpoint and so on?

thiagofm13:03:24

I've wanted to do something that just gets some information from github api and saves it in a datascript key, so I can query later

thiagofm13:03:02

But then I had to learn a bit of core.async and so on, too much stuff I guess.

seanirby13:03:14

iwankaramazow: the send function is declared in the reconciler

iwankaramazow13:03:40

you declare (with the above send function) at your reconciler :send (transit-post "/url-of-your-endpoint")

seanirby13:03:59

whoops meant to direct that to thiagofm

thiagofm13:03:42

So I can just specify a single endpoint to the reconciler?

thiagofm13:03:27

I need to make like 3 reqs to the github API for me to get the information I need(I have to go through the links there and so on)

thiagofm13:03:40

In the reconciler I see:

thiagofm13:03:41

(def reconciler (om/reconciler {:state (atom {}) :normalize true :parser (om/parser {:read p/read :mutate p/mutate}) :send (util/transit-post "/api")}))

iwankaramazow13:03:04

You have to write your own send function which knows what queries to send to different parts of the github api

seanirby13:03:35

that hardcodes it to one endpoint, your send function could decide which endpoint to call

thiagofm13:03:28

Ohhh, I see

thiagofm13:03:03

This is a bit counter intuitive, I've expected my read method to handle that

iwankaramazow13:03:01

@thiagofm: my first remote was a classic rest api, in my send function I checked what the query was, with cond I determined the exact url of my rest api, and used that to send it off

thiagofm13:03:10

Now I kind of get it, I guess. The query in my read function can be accessed from the send function, then I can decide on what to do

thiagofm13:03:32

I just wonder why this isn't done by the read

thiagofm13:03:56

I could just make a request and return it

thiagofm13:03:04

and then I could just read by this req

iwankaramazow13:03:04

Separation of concerns

thiagofm13:03:30

Wouldn't it be a good idea to treat the data you query in a datascript db the same way you would a data that is ajax requested?

thiagofm13:03:36

It's all data that's somewhere

thiagofm13:03:49

I'll do some coding, maybe I'll reach enlightenment once I try it out

iwankaramazow13:03:49

I don't fully understand your question I think

thiagofm13:03:42

I see the kind of data I get from a database the same kind of data I could be getting from an endpoint. Even some databases have rest endpoints

thiagofm13:03:05

Then you could build some logic to cache that data and also to expire that cache

thiagofm13:03:46

I expected things to be in the same way in om, but it's not. I guess I have to find out why it's not that way

iwankaramazow13:03:28

There are a few problems with rest, for example: too many request, when you have an endpoint which receives just queries

iwankaramazow13:03:42

you can ask exactly the data you need

iwankaramazow13:03:40

(given a backend that can interpret those queries)

thiagofm13:03:53

That part is okay, I guess. I thought om worked like this: You have a couple of query/read functions, as an example: :users(list of users). You specify in every component it's own queries(could be querying the local state database, or a remote, doesn't matter). I can then do some funky mutation which changes the attribute of an user, and then I send like, hey, re-render the user listing component, and it would find out the queries related to that and expire their cache and re do it if needed

thiagofm13:03:03

I feel I'm getting things completely wrong πŸ˜›

thiagofm13:03:19

My read repositories from github could be something like (defmethod read :github-repos ...) and I could just query and handle it the way I've wanted

iwankaramazow13:03:22

-> co-located queries on components: descriptions of the data it needs -> parser: interpret those co-located queries -> mutations trigger a re-read -> you have to implement that specific read function in a particular way if you want to expire your 'cache' (aka local database) or send it of to a remote

thiagofm13:03:23

Okay, so I write inside the read whether if I want to expire it's value or return it?

iwankaramazow13:03:58

(defmethod read :github-repos
  [{:keys [query state]} key _]
  (let [st @state]
    (if (nil? (get st key))
      {:remote true}
      {:value (get st key)})))

thiagofm13:03:13

Okay, so using datascript I would have to verify if I want to expire it, if not, I make a query to datascript looking for the the key that has saved the value I want

thiagofm13:03:49

How does the send function knows who asked for a remote read?

iwankaramazow13:03:25

it knows the query

iwankaramazow13:03:24

I think there's a re-read happening when the results arrive from that query, based on that query

thiagofm13:03:27

So... (defn transit-post [url] (fn [{:keys [remote]} cb] (.send XhrIo url (fn [e] (this-as this (cb (t/read (t/reader :json) (.getResponseText this))))) "POST" (t/write (t/writer :json) remote) #js {"Content-Type" "application/transit+json"}))) The cb would return the github repos json/map

thiagofm13:03:41

At which point does it would become a key inside datascript I could query?

thiagofm13:03:47

I think for datascript I would have to do mutate the db to save that key

iwankaramazow13:03:50

the cb is a callback provided by Om which merges things

iwankaramazow13:03:18

I think with datascript you'll have to do some stuff you can't do out of the box

iwankaramazow13:03:30

Datascript integration isn't 100% ready yet

thiagofm13:03:50

Hm, I think I understand now, this was the missing part, thanks a lot!

thiagofm13:03:21

Without datascript this (get st key) is trying to get a key from the global state

thiagofm13:03:26

With datascript I have to do a query

thiagofm13:03:08

I think I can try to pass the reconciler and do a mutation query to save the key, in a generic way I can use also in other places

iwankaramazow13:03:22

sounds like a plan

iwankaramazow13:03:36

haven't looked quite at datascript

seanirby13:03:50

are there any good dev tools i should know about besides figwheel and cljs-devtools? I seem to recall coming across a list but I forgot to bookmark it

thiagofm14:03:42

cider nowadays has clojurescript jack in, it works perfectly

seanirby14:03:46

yeah I'm using CIDER, its great simple_smile

seanirby14:03:30

that new "enlighten mode" is especially cool

iwankaramazow14:03:16

enlighten mode?

seanirby14:03:42

yeah it shows the value of of locals in the buffer

seanirby14:03:28

looks like it only works with clojure buffers at the moment, same with the debugger

thiagofm14:03:11

Yeah, heard about the debugger

thiagofm14:03:02

Any idea how could I access the reconciler inside a read? 😞

haywood15:03:16

what do you need from the reconciler?

thiagofm15:03:42

@haywood: I need to do a workaround to update a remote inside a read

thiagofm15:03:47

because of datascript

thiagofm15:03:22

I'll just pass it as a composed prop

haywood15:03:39

you could just require it too right since it's a singleton?

thiagofm15:03:22

It's defined in core.cljs file, it would create a circular dep issue no?

haywood15:03:03

yea I'd just create a reconciler file that sets the parser / state up and import that from both files

haywood15:03:13

but if passing it as computed props works for you do that

thiagofm15:03:35

Makes sense

thiagofm15:03:11

I'm not that used to holding information about which file gets loaded before in my mind as clojure has, cool simple_smile

thiagofm16:03:24

Assert failed: Circular dependency detected, haxlife.components.code -> haxlife.data.query -> haxlife.reconciler -> haxlife.data.query

thiagofm16:03:28

didn't work =/

thiagofm17:03:16

@iwankaramazow: okay, so it seems when I set {:remote true} as response of a read, the reconciler sends to my send function this: {:remote [:remote/github-repositories]}

thiagofm17:03:52

So it sends the name of the read query, good. How do I send parameters?

cmcfarlen17:03:46

@thiagofm: You send them in a list [({:remote-items [:a πŸ˜› :c]} {:param 1})]

thiagofm18:03:53

@cmcfarlen: where do I send this?

thiagofm18:03:08

I have now: {:remote true}

cmcfarlen18:03:23

If the key in the query that you send to the remote contains params, they will be sent along. So if you reader returned {:remote true} for :remote-items, then {:param 1} would also be sent to the server.

cmcfarlen18:03:41

So you just include params in the query of your component

thiagofm18:03:25

@cmcfarlen: okay, makes sense, let me see

thiagofm18:03:09

@cmcfarlen: okay, it worked

thiagofm18:03:52

@cmcfarlen: can IQueryParams be dynamic? I would like for example to pass the reconciler for example(because of... reasons)

cmcfarlen18:03:12

@thiagofm: yes, these can be dynamic. (om/set-query! component {:params {:param 42}})

thiagofm18:03:09

@cmcfarlen: where can I use this function? In the component? In the read?

thiagofm18:03:53

I mean, what is the component variable? Where do I have access to it?

cmcfarlen18:03:22

In the component. If you need to add/change params from read, you can modify the ast of the query. You see, {:remote true} can be used to send the query as is. But you can also give a modified ast and it will send that instead.

cmcfarlen18:03:00

Something like: {:remote (assoc (:ast env) :params {:more-different-params 123})}

cmcfarlen18:03:00

The idiomatic way though is to use set-query! and then substitute into the query with the ?param symbols

thiagofm18:03:42

But how would it work? Let's say I have a component called "Code". Inside that component, I do (om/set-query! this {:language "haskell"}), but then to render this component, the query is already supposed to be made, no? So I'll never see the language as " haskell" @cmcfarlen

cmcfarlen18:03:14

set-query! triggers a re-read of the query and will call the parser again

thiagofm18:03:42

(defui Code static om/IQueryParams (params [this] {:language "ruby"}) static om/IQuery (query [this] '[(:remote/github-repositories {:language ?language})]) Object (render [this] (let [github-repositories (:remote/github-repositories (om/props this))] (om/set-query! this {:language "haskell"}) ; (dom/div nil github-repositories))))

thiagofm18:03:53

Why do I still get {:language "ruby"} ? @cmcfarlen

cmcfarlen18:03:20

(om/set-query! this {:params {:language "haskell"}})

thiagofm19:03:48

@cmcfarlen: Still prints

thiagofm19:03:50

reconciler.cljs?rel=1458408916674:11#object[cljs.core.async.impl.channels.ManyToManyChannel] reconciler.cljs?rel=1458408916674:11(:remote/github-repositories {:language "ruby"}) reconciler.cljs?rel=1458408916674:11{:remote [(:remote/github-repositories {:language "ruby"})]}

matthavener19:03:20

might make sure this is actually a Code component

cmcfarlen19:03:26

@thiagofm: hmm, you might also try to call it outside of the call of the render fn. So put the set-query! call in an event from a button or something. render is meant to be pure

thiagofm19:03:09

@matthavener: how do I get the component?

matthavener19:03:24

oh nevermind I see the code, I agree.. dont set-query! in render

cmcfarlen19:03:53

@thiagofm: you can call it from componentDidUpdate.

Object
   (componentDidUpdate [this prev-props prev-state]
       (om/set-query! this {:params {...}}))
   (render [this] ...)

noonian19:03:16

Hey all. I just pushed a small client-only example to github. We had a small meetup session talking about om next and this is the result: https://github.com/noonian/om-next-counters-example

cmcfarlen19:03:37

@noonian: Nice! Looks good!

noonian19:03:46

@cmcfarlen: thanks!

thiagofm19:03:35

@cmcfarlen: still didn't work 😞 I'll try more tomorrow, thanks a lot for your help

tomjack19:03:31

why do sorted/counters in the parser, vs sorting other/counters in render?

tomjack19:03:48

(sort of a general question, not really specifically about the example..)

noonian19:03:02

@tomjack: Initially I just had counters, but someone asked a question from the context of re-frame, and how they would do something with a re-frame subscription, and it kind of grew out of that. Sorting in the render should work fine also, but you’d want to read the :other/counters key instead of the :sorted/counters key in the mutating transactions.

tomjack19:03:55

I think I still don't understand the rereads -- I expected :count there. which seems to work as long as the :sorted/counters query includes :count

noonian19:03:25

wow, :count does seem to work, but I don’t understand why simple_smile

tomjack19:03:43

ah.. it looks like you don't need to reread :other/counters if you do the sort in render. but rereading nothing does not work with :sorted/counters

noonian19:03:02

I thought I needed to re-read :sorted/counters in order to get om to re-render the root component.

noonian19:03:12

and that does work

tomjack19:03:12

oh, yeah, you're right -- with sort in render you do need to reread :other/counters. otherwise the counters update in the sorted list, but they aren't resorted...

noonian19:03:23

re-reading :count also seems to work though, and it must be that om is smarter than I thought and knows that the RootView needs to be re-rendered

symfrog19:03:36

What is the idiomatic way of communicating errors back to a component that initiated a mutation in om next? Errors could occur in the mutation function (e.g. parameter validation failure), the send function (e.g. 500 error for remote mutations), or the merge function. I have not been able to find a clean way of communicating the possible errors back to the component that requested the mutation.

tomjack19:03:41

yeah. part of my question is how stuff like :sorted/counters can break this smartness

tomjack19:03:02

(by depending on stuff which is not necessarily in the query used -- though in your case :count is there)

tomjack20:03:23

I guess the :sorted/counters read function will just be broken if :count is not in its query simple_smile

noonian20:03:49

Yeah, it means any mutating transactions have to know to cause the sorted list to rerender

tomjack20:03:27

this doesn't seem as bad as I thought given that in the sort-in-render alternative we still need to reread :other/counters

tomjack20:03:31

anyway, thanks for the example!

noonian20:03:30

np, thanks for checking it out!

tomjack20:03:39

er, I forgot that :count apparently works. so if we reread :count we don't need to reread :other/counters. maybe...?

noonian20:03:01

well, I’m not changing it to :count until I understand why it works heh

noonian20:03:13

since you still have to re-read a key either way

danielstockton20:03:20

Anyone using datascript with alpha31 and merging remote state? I got a new error...

danielstockton20:03:00

I think it tries to update queues-sends in state, is that new?

anmonteiro20:03:39

@danielstockton: not related to sends

anmonteiro20:03:42

It's a known issue and will only be solved once we move queries out of the app state

danielstockton20:03:28

Ok, thanks. Most things seem to work, despite the error.

danielstockton20:03:45

I just have to steer clear of queries

thiagofm23:03:09

How do I override ast in a read?