Clojurians
#hoplon
<
2016-02-17
>

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

thedavidmeister10:02:12

hey, so i have a q about how attributes work inside a loop-tpl

thedavidmeister10:02:45

i did this (using datascript):

thedavidmeister10:02:59

(defc= sorted-values
  (concat
    (->>
      (d/q '[:find ?id ?data :where [?id :data ?data]] conn)
      (sort-by first))
    '([nil nil])))

thedavidmeister10:02:13

(loop-tpl :bindings
    [[id data] sorted-values]
    [(if (not= @data "")
     (input
        :value data
        :data-id @id
        :input #(d/transact! conn
          (if @id
            [{:db/id @id :data @%}]
            [{:data @%}]))
        :keyup #(if @id (d/transact! conn
          (if (= "" @%)
            [[:db.fn/retractEntity @id]]
            [{:db/id @id :data @%}]))

thedavidmeister10:02:51

so, the :value updates as i expect (the conn is a cell so every time i update the db it reflows the inputs)

thedavidmeister10:02:21

but the :data-id doesn’t seem to update at all once an input has been created

dm310:02:44

because you derefed it

thedavidmeister10:02:10

tries not to deref

thedavidmeister10:02:22

also, cells + datascript is really cool :simple_smile:

thedavidmeister10:02:45

is there an easier way to do it than

thedavidmeister10:02:48

(defn conn-cell-from-db
  "Mimics datascript conn-from-db but builds a compatible javelin cell"
  [db]
  (cell db :meta { :listeners (atom {}) }))

thedavidmeister10:02:00

i mean, that’s easy, but is there a more “right” way?

dm310:02:16

I have a little cell-q helper:

(defn cell-q [query & params]
  ((formula #(apply d/q query % params)) db))

dm310:02:33

and db is a cell that is being reset on every transaction

thedavidmeister10:02:05

yay, works, @dm3 thanks

dm310:02:49

then you

(def x (cell-q '[:find ?x  :where [?x :is 1]]))

(defc= derived (first x))

thedavidmeister10:02:02

aah cool, well i noticed that the only thing datascript checks for to see if conn is valid is IDeref

thedavidmeister10:02:18

so i just used a cell instead of an atom and rewrote it :simple_smile:

thedavidmeister10:02:33

you can use transact! straight on the cell then

dm310:02:45

I have a few more abstraction layers

thedavidmeister10:02:27

do you know if anyone pinged tonsky about a direct collaboration?

thedavidmeister10:02:33

hoplon + datascript seems like a great match

dm310:02:51

I don't think he'll be very interested

dm310:02:05

what would you imagine as things needing integration?

thedavidmeister10:02:35

basically something like what i did above, but also “officially” not doing anything that would break cells

thedavidmeister10:02:43

very little would need doing now

thedavidmeister10:02:52

but of course i’m nervous that the api might shift in the future...

dm310:02:44

don't think it will shift dramatically

dm310:02:57

you can always use my approach :simple_smile:

thedavidmeister11:02:53

i’ve got bigger problems right now

thedavidmeister11:02:57

like being a n00b :wink:

thedavidmeister11:02:56

is there a way to do a loop-tpl with a let @dm3 ?

dm311:02:13

what do you mean?

thedavidmeister11:02:24

or is that supposed to be :bindings

thedavidmeister11:02:52

(loop-tpl :bindings [
    data (d/q '[:find ?id ?data :where [?id :data ?data]] conn)
    sorted (sort-by first data)
    sorted+final (concat sorted '([nil nil]))
    [id data] sorted+final]

thedavidmeister11:02:19

(defc= inputs-state (let [
    state (d/q '[:find ?id ?data :where [?id :data ?data]] conn)
    sorted (sort-by first state)
    sorted+final (concat sorted '([nil nil]))]
    sorted+final))

(u/outer-dom
  (u/the-header)
  (main :id "main"
    (loop-tpl :bindings [[id data] inputs-state]

thedavidmeister11:02:22

that works though

thedavidmeister11:02:53

@dm3: does that make sense?

dm311:02:32

think there's cell-let for that purpose

dm311:02:09

you would probably have to wrap (d/q ...) in a (cell=

dm311:02:15

for that one to work in bindings

thedavidmeister11:02:56

so :bindings doesn’t like things that aren’t cells?

thedavidmeister11:02:15

is there any particular overhead to nesting cell=?

thedavidmeister11:02:25

or can i pretty much chain it as much as i want?

dm311:02:47

there's always overhead

dm311:02:57

if your database isn't too big you'll be fine

dm311:02:29

otherwise you just need to keep in mind that when anything in the database changes, all of your queries will get rerun

thedavidmeister11:02:53

ah yeah, for re-running queries, that’s obvious

thedavidmeister12:02:02

(defc= inputs-state (let [
    state (d/q '[:find ?id ?data :where [?id :data ?data]] conn)
    sorted (sort-by first state)
    sorted+final (concat sorted '([nil nil]))]
    sorted+final))

thedavidmeister12:02:12

(defc= state (d/q '[:find ?id ?data :where [?id :data ?data]] conn))
(defc= state:sorted (sort-by first state))
(defc= state:sorted+final (concat state:sorted '([nil nil])))

dm312:02:19

that's very insignificant

dm312:02:27

just a few calls to apply, etc

dm312:02:39

should be on the order of couple microseconds

thedavidmeister12:02:50

sounds good :simple_smile:

thedavidmeister12:02:06

@dm3 also, do you have any experience with what “too big” might be in practice?

thedavidmeister12:02:16

before you notice queries getting sluggish

dm312:02:48

no :confused:

dm312:02:10

I have a couple of optimizations in my sleeve if it gets out of hand

dm312:02:21

(running queries when relevant data changes, etc)

dm312:02:32

splitting the db in the worst case

dm312:02:39

hopefully won't need to do that

dm312:02:56

btw, do you sync the local db to the remote?

thedavidmeister12:02:16

haven’t figured that out yet

thedavidmeister12:02:31

i wrote my first datascript query yesterday :stuck_out_tongue:

dm312:02:45

I've abandoned that idea as either you have a mirror db on the server for each client or you have a headache of mapping entity ids between different dbs

dm312:02:54

so currently I'm working with the model where a client generates an event, applies it to the db locally and sends to the remote which then gets propagated to other clients

thedavidmeister12:02:45

what do you mean by “an event”?

dm312:02:37

like (comment-added :user-id 'x, :text 'y)

dm312:02:03

it carries intent

dm312:02:16

you could skip that step and derive it from the datascript transaction

dm312:02:21

if the data model is simple

dm312:02:26

or more CRUDy

thedavidmeister12:02:04

oh yeah, i saw someone had some code to push the transactions to the remote

dm312:02:10

no, it's fine

dm312:02:45

you just lose some of the clarity if you need to do some complicated actions in response to a transaction

dm312:02:20

you need to reverse engineer the action which generated the transaction

thedavidmeister12:02:07

why would you need to perform complicated actions on the remote in response to syncing transactions?

thedavidmeister12:02:32

wouldn’t you do the complicated stuff in the client and then just push the transactions through as-is?

dm312:02:17

some things you can only do on the remote, no?

dm312:02:27

actually take decisions

dm312:02:34

as there are many clients

dm312:02:46

imagine several people editing the same document

dm312:02:46

so the remote is the synchronization point which decides what's the ultimate state

dm312:02:55

client may think it's right

dm312:02:07

but it will get corrected in case of a conflict

thedavidmeister12:02:16

ah, i had thought that was what the transactor was supposed to help sort out

thedavidmeister12:02:25

because it’s serial

dm312:02:28

what transactor?

thedavidmeister12:02:37

isn’t there a datomic thing somewhere?

dm312:02:54

I wasn't including that in the picture

dm312:02:06

but if you even have datomic for persistence

dm312:02:33

you can either adopt a dumb strategy of "last update wins" - and that works for a huge amount of cases

dm312:02:57

have something like CRDT where the updates are monotonic and conflicts resolved automatically

dm312:02:23

or make decisions intelligently based on what happens (events) - e.g. operational transformation (how google docs work)

thedavidmeister12:02:19

well the transactions have times on them don’t they?

thedavidmeister12:02:27

if you had a datomic

thedavidmeister12:02:44

wouldn’t you get both updates, with a log, and the current on is “last update wins”?

thedavidmeister12:02:54

*haven’t actually used datomic*

dm312:02:44

yes, you'd get that (as with any other db) if you just overwrite

dm312:02:56

I was just trying to make a point that you can't always assume that strategy

thedavidmeister12:02:49

but the difference is you’d have an audit trail

thedavidmeister12:02:04

if you were a user and were like “wtf just happened?"

dm312:02:25

you get the same if you use another event-sourced solution

thedavidmeister12:02:29

you’d see “you updated” and “bob updated 5 seconds after you”

dm312:02:44

I'm not planning on using datomic though

thedavidmeister12:02:41

ok sure, so yeah

thedavidmeister12:02:59

you need to make something domain specific

thedavidmeister12:02:05

to keep track of “what happened"

thedavidmeister12:02:25

where “what happened” is a bit easier to understand than a stream of transactions

dm312:02:59

hence I don't like using any sort of auto-syncing solutions

dm312:02:17

but then again - I haven't done a CRUD app in a very long time

thedavidmeister12:02:51

what do you normally work on?

thedavidmeister12:02:01

also, is there a reason to specifically not use datomic?

thedavidmeister12:02:14

it seems like it would be useful for keeping everything in check

dm313:02:17

it is nice

alandipert13:02:55

wow, lotta cool datascript/cells goin on i nhere

thedavidmeister13:02:35

yeah, learning about it :simple_smile:

thedavidmeister13:02:54

i had written something in ~80 lines that used a vector storing state in localStorage

onetom13:02:22

Im also very tempted to pull in datascript, so thanks for the examples!

thedavidmeister13:02:22

trying to get the same thing in ~40 lines with datascript

thedavidmeister13:02:48

probably as much as i can do tonight

thedavidmeister13:02:54

hopefully I’ll get my tests passing tomorrow :simple_smile:

thedavidmeister13:02:44

(defn conn-cell-from-db
  "Mimics datascript conn-from-db but builds a compatible javelin cell"
  [db]
  (cell db :meta { :listeners (atom {}) }))

(def conn (local-storage (conn-cell-from-db (d/empty-db {})) ::conn))

(defc= state (d/q '[:find ?id ?data :where [?id :data ?data]] conn))
(defc= state:sorted (sort-by first state))
(defc= state:sorted+final (concat state:sorted '([nil nil])))

(defn attribute-selector
  ([attribute] (str "[" attribute "]"))
  ([attribute value] (str "[" attribute "=\"" value "\"]")))

(u/outer-dom
  (main :id "main"
    (loop-tpl :bindings [[id data] state:sorted+final] [
      (input
        :value data
          :data-id (cell= (or id "final"))
          ; input occurs on keydown but after the input's value has been updated
          ; c.f. keydown that occurs before the value has changed.
          :input #(d/transact! conn
            (if-not (= "" @%)
              (if-not @id
                ; create
                [{:data @%}]
                ; update
                [{:db/id @id :data @%}]
                )
              (if @id
                ; delete
                [[:db.fn/retractEntity @id]])))
          ; Move the cursor on keyup for certain states. This allows the DOM to
          ; fully reflow from db updates before we try to jump the user's
          ; context around.
          :keyup #(let [
            input-blank? (= "" @%)

            backspace? (which-key/pressed? :backspace %)
            enter? (which-key/pressed? :enter %)

            select-last-id! (fn [] (do! (attribute-selector "data-id" (-> @state:sorted (last) (first))) :select))
            select-final-input! (fn [] (do! (attribute-selector "data-id" "final") :select))]
            (cond
              (and input-blank? backspace?) (select-last-id!)
              enter? (select-final-input!))))])))

thedavidmeister13:02:57

@onetom: that’s where i got to tonight, if you’re wanting to play around :simple_smile:

thedavidmeister13:02:58

the real advantage will be moving from individual inputs (which is about all i could easily manage with a vector) to a variety of form elements

thedavidmeister13:02:06

keen to try and do that

flyboarder19:02:04

Anyone have an idea why png’s are getting a 404 in my app?

flyboarder19:02:39

Ah! I figured it out! :asset-paths #{"resources/assets”} is not on the class path