Fork me on GitHub
#fulcro
<
2021-01-07
>
JAtkins01:01:07

Dunno if this would come in useful, but I just spent a bit of time to get a rough translator going from fulcro rad attributes => malli-schema format. It will also figure out what entities you have (based of of ao/identities ) and generate the joins required. It has lots of rough edges, but it's good enough for my use-case of dogfooding some random initial state when I need something. I didn't use spec because it's nearly impossible to generate spec1 at runtime. A better solution might be to override defattr to create a spec definition at compiletime, but I only thought of that after I wrote this 🙂. https://gist.github.com/JJ-Atkinson/796d8e124edf684def2283dd9759515d

metal 6
Timofey Sitnikov11:01:24

Trying to get the https://github.com/fulcrologic/fulcro-rad-demo to add an Account in SQL configuration. Everything works fine till I try to add an account, then I get the error

originalMessage: "Data conversion error converting \"OTHER to ENUM\""
Attached is the complete error listing. What would be a good way to fix this? I think that the issue is in the https://github.com/fulcrologic/fulcro-rad-sql/blob/develop/src/main/com/fulcrologic/rad/database_adapters/sql/resolvers.clj. It may be possible to wrap enum into as-other as can be seen in the https://cljdoc.org/d/seancorfield/next.jdbc/1.1.613/doc/getting-started/tips-tricks#working-with-enumerated-types. Looking for advice on how to fix this.

tony.kay13:01:01

The SQL plugin is very alpha. Enumerations, in particular, are known not to work well. I’m actively interested in finding a maintainer, and currently don’t have time to work on it.

Timofey Sitnikov10:01:16

@tony.kay understood, I just wanted to throw the question out there, in case someone would recommend a good solution that I can start with.

Timofey Sitnikov10:01:26

@tony.kay also sorry about the bad pull request. I started the pull request right after i committed just the addition to the require block, I thought that github would snapshot the commit, but then I pushed two more commits. After that I pressed complete pull request button and the two later commits ended up being part of it. I feel bad about it because I do not like to waste peoples time going through unnecessary code.

tony.kay17:01:35

no worries. The solution could be “find standard SQL that works for all dbs and use that in the plugin” or “write a protocol for the diffs, and use that to access the dbs from the adapter”. Requires some R&D

Björn Ebbinghaus13:01:01

@tony.kay What is this line in fulcro.components for?

#?(:clj
   (defn -legal-keys
     "PRIVATE. Find the legal keys in a query. NOTE: This is at compile time, so the get-query calls are still embedded (thus cannot
     use the AST)"
     [query]
     (letfn [(keeper [ele]
               (cond
                 (list? ele) (recur (first ele))      <- THIS ONE ================================================================================
                 (keyword? ele) ele
                 (is-link? ele) (first ele)
                 (and (map? ele) (keyword? (ffirst ele))) (ffirst ele)
                 (and (map? ele) (is-link? (ffirst ele))) (first (ffirst ele))
                 :else nil))]
       (set (keep keeper query)))))
https://github.com/fulcrologic/fulcro/blob/ee9545df41795d1acd5365c1926036740a5da98e/src/main/com/fulcrologic/fulcro/components.cljc#L1311 I am trying to figure out, if/how I can use parameters directly in queries. Like: {:query [:comment/author (:comment/body {:truncated? true})]} I was hoping that when a query is made to the client-side database, the parameter will simply be ignored, but the query will still be sent to the backend unchanged.

tony.kay13:01:40

It’s used to generate compile-time errors around defsc to prevent common errors. Parameters don’t make sense in static component queries, and the generalized load support allows you to add them when you need them. This makes the use of dynamic queries that add them also largely irrelevant. Fulcro could support that, but I have no intention of doing so at the moment. The earlier versions technically did support this, but no one seemed to use it, including me (and not a single complaint from removal).

tony.kay13:01:04

It was a source of complexity that I chose not to move forward with, given the amount of complexity it added to everything. I judged it “incidental”. I’m not dead-set on that judgement “forever”, but until I see really compelling use-cases that cannot be solved (reasonably well) with the existing APIs I have no interest in making things more complex based on the occasional “preference” to embed them that way.

tony.kay13:01:23

My recommendation is to use namespaced keys in the params argument of load, move those into your server parsing env (so they propagate to children). The namespacing allows for a form of “targeting” of the query params. Granted that solution is not perfect, but in the real world with the real features of load, it has solved every case I’ve needed very easily.

💯 3
tony.kay14:01:46

Sorry, you asked about the specific line. Yes, that line is there to deal with the fact that one could technically embed parameters in queries.

tony.kay14:01:11

and the error checking doesn’t care about them

tony.kay14:01:17

When I talk about the query parameter stuff above, I mean that you used to be able to do this: [(:prop {:start ?start})] and then later call set-query to bind ?start to something. That’s the parameter support I dropped.

tony.kay14:01:26

you can still call set-query and put parameters in the query of a component. It could technically have some bugs (I do not imagine it does), because no one uses it to my knowledge, but try it if you wish.

Björn Ebbinghaus14:01:31

I am trying to. When writing (:comment/body {:truncated? true}), like above, I just get an Invalid expression error from cljs. When I quote the expression, I get was destructured in props, but does not appear in the :query I am trying to figure out how to fix this for me.

Björn Ebbinghaus14:01:47

It doesn’t matter so much whether it works or not. It’s not really worth my time right now, but curiosity grabbed me.

Björn Ebbinghaus14:01:02

I saw the specific line and though: “Great, I am not the first one to try this” 🙂

tony.kay15:01:20

right, I don’t have tests covering the case for query params in component static queries but there is some legacy bit here and there that made it through the removal. Given the bug, then only way to do it right now is via set-query!

tony.kay15:01:44

which you actually need to use anyway, since parameters (by their nature) vary

tony.kay15:01:18

My general reasoning was: You need to pre-set bindings and parameters before they make sense, so why not just use set-query?

tony.kay15:01:42

but because all that introduces the need for logic in the same code points as load, why not just use load params?

tony.kay15:01:37

The answer, of course, being you want the params in a particular spot. My workaround mentioned before works quite well for me, but you still have set-query! if you really really need it.

Björn Ebbinghaus14:01:52

@tony.kay While I am already at it: Here are two questions that I thought about and would love your opinion whether these things make sense or not. I am aware that these things are just “sugar” and not a necessity. 1. How would I write a function for something like an … “anonymous component” for query purposes? I often have to write components just for the query:

(defsc Child [_ _] 
 {:query [:id]
  :ident :id})

(defsc Something [_ {:keys [children]}]
 {:query [{:children (comp/get-query Child)}]}
 (str "I have " (count children) " children!")
)

; to

(defsc Something [_ {:keys [children]}]
 {:query [{:children (comp/sc [:id] {:ident :id})]}     ; <- something like this   ; or (comp/sc {:query [:id] :ident :id})
 (str "I have " (count children) " children!")
)
2. Would is make sense to allow idents of length one for singletons?
(defsc NavDrawer [_ _]
 {:query [:open?]
  :ident (fn [] [:component :nav-drawer])

(defsc Root [_ _]
 {:query [{:nav-drawer (comp/get-query NavDrawer)}]}) 

; The db:
{:nav-drawer [:component :nav-drawer]   ; This seems redundant for singletons
 :component {:nav-drawer {:open? false}}

;;; instead NavDrawer with a single item ident
(defsc NavDrawer [_ _]
 {:query [:open?]
  :ident (fn [] [:nav-drawer]))

; would lead to this db:
{:nav-drawer {:open? false}}

tony.kay15:01:41

1. Technically you just need to add the metadata that get-query adds, and have the :component in that have a component options ident (which in cljs is just an js object with some special fields). The only thing special about it is the component used for normalization; however, I do not see why you’d want that, because you should be doing the rendering in the child, and therefore should be writing a child anyway. Writing a separate component just for a query is a very rare thing indeed (your example is a contrived use-case). 2. you can certainly write a macro that transforms that, but I don’t want to add yet another syntax sugar onto a macro that already has so many little things like that. On the one hand it’s “par for the course”, but on the other what “table name” am I to auto-select for you? Also, since keywords without a vector mean :x => [:x (:x props)] having another special variant [:x] => [:component/id :x] along with [:x :y] => [:x (:y props)] or (fn [] literal-ident) is adding to a thing that is already a bit excessive, and doesn’t even follow the “sugar pattern” of pulling something from props to generate the ident.

Björn Ebbinghaus15:01:02

To 2.: Is it really another case? For singletons a table-name doesn’t make sense. I thought not about a keyword without a vector, but a vector with a single entry as a literal-ident (fn [] [:x]) Like you could for a normal map in clojure: (get-in state [:nav-drawer]) and (assoc-in state [:nav-drawer])

tony.kay15:01:25

I see what you’re getting at. I definitely do not want to introduce that complication. It would affect a lot of things for no tangible benefit.

tony.kay15:01:55

(state would then have to be able to contain card 1 vectors as idents, which would confuse all manner of things)

Björn Ebbinghaus20:01:18

Hm. I thought that could be as simple as remove any checks if an ident is exactly card. 2. Ok. Thanks for your reply.

tony.kay15:01:49

A more solid use-case for (1) is (df/load! :kw (sc ident query)) where you want normalization of some data into the client that doesn’t have UI of that shpe

tony.kay15:01:34

See configure-component! for ideas on making the query thing, but be careful: the component registry is normally part of the story, and your use-case probably doesn’t want to register, so you don’t really want all the mess that configure component does.

tony.kay15:01:02

To test if your thing works, just make sure get-query and get-ident (both arities) work on it.

njj16:01:36

Is it possible to watch changes in props of a child component? I want a parent component to be able to see if a child (form) component is valid (I’m using the non-spec validators)

njj16:01:14

Oops didn’t mean to pin that 😄

tony.kay16:01:17

the props of the child flow through the parent

tony.kay16:01:20

yeah, I figured 🙂

tony.kay16:01:26

The trick is that if you use an optimized call the re-renders just the child, then you need to make sure the parent is refreshed (usually by using something like :only-refresh. Depends on renderer. Oh, in F2, which I think you’re using, you’d include a parent prop in the follow-on read

njj16:01:05

ah I see.. so on any updates to the child just make sure those transactions are also updating the parent ident

tony.kay16:01:08

(transact! this [(update-field …) :parent-prop])

👍 3
njj16:01:16

got it, thanks

tony.kay16:01:31

in F3 I figured out that the targeted refresh is usally not needed for performance (if you do other things right), so default is to render from root, which in turn makes doing that a non-issue…you don’t even have to think about it. It pushes the potential complexity off to an optimization step instead of troubling you with it from the outset as a pre-optimization

njj17:01:33

Speaking of validation, on initial load my fields are “unchecked” but once I edit one of them, the rest of them turn invalid. Is there a way around this? I’d rather not show validation messages until the user has tried to modify that field

njj18:01:56

I guess I just don’t see why editing one field would make the rest invalid

tony.kay18:01:56

“edit one of them” is never and has never been the trigger

tony.kay18:01:06

mark-complete is what changes that

tony.kay18:01:16

and it can be called for a field or the whole form, but it is in your control

njj18:01:00

I see.. so what is the recommendation in this scenario? The onChange is causing the whole form to be invalid at this point

tony.kay18:01:22

add the field argument to mark complete

Björn Ebbinghaus20:01:57

Does anyone has an example with two nested dynamic-routers, where both routers have a parameter in their route-segment? I am stuck with initializing the state correctly.

tony.kay21:01:21

what are your route segments?

Björn Ebbinghaus21:01:17

[„decision“ :process/slug] And [„proposal“ :proposal/id] or even [„proposals“] Thinking about it. I have problem placing another dynamic router in a target with a parameter. The second parameter isn‘t even needed.

tony.kay22:01:52

should nest fine, but you need to make sure you pass a complete route to it…i.e. the vector of a route MUST have 4 elements in order to match

tony.kay22:01:17

if you fail to specify a bit of the segment the routing will stop at that point and assume the sub-routes are “right” already

Björn Ebbinghaus22:01:32

But how do I initialise the inner router?

Björn Ebbinghaus22:01:23

It can‘t be initialised on app startup because of the parameter.

tony.kay22:01:34

sure it can, you can initialize state and set root with set-root!, then use dr/route-to!

tony.kay22:01:40

then mount w/o initialize

tony.kay22:01:27

I use a “ready” flag on the root node to prevent rendering until things have settled (i.e. a ok-result handler on loading app config or something)

Björn Ebbinghaus00:01:30

Ok something is kind of working now… Having the situation, where I have two deferred routings with the same ident RootRouter routes to Process. and the ProcessRouter (inside Process) routes to ProcessHome. Process offers UI for every sub page of the router. [:process/slug :process/title] ProcessHome has details for the Process. [:process/slug :process/title :process/description]

tony.kay01:01:56

ah, that is a bit of a problem 🙂

Björn Ebbinghaus01:01:48

@tony.kay Hm. Now I have noticed that I will often have the case that the children have the same ident as their parents. 😕 Do you have a pointer how to avoid this messing with my routing? Or nested routers in general? I have to admit that I’m pretty lost in figuring out how to do it “right”.

tony.kay02:01:45

What are you doing that is causing your children to have the same idents as the parent?

tony.kay02:01:13

especially as routes? It seems like in a path once you’ve identified a thing, that the children of that thing would not be the thing

Björn Ebbinghaus11:01:17

Ok. I stripped down my case to the following code. I think writing it down like this is easier than me trying to explain it to you.

(defsc ProposalList [_ _]
 {:query [::process/slug {::process/proposals (comp/get-query Proposal)}]
  :ident ::process/slug
  :route-segment ["proposals"]}
 (button {:onClick open-add-proposal-modal!} "Add a new proposal to this list!"))

(defsc ProcessHome [_ _]
 {:query [::process/slug ::process/title ::process/description]
  :ident ::process/slug
  :route-segment ["home"]}
 (button {:onClick open-add-proposal-modal!} "Start with a new proposal!"))

(defrouter ProcessRouter [_ _]
 {:router-targets [ProcessHome ProposalList]})
(def ui-process-router (comp/factory ProcessRouter))

(defsc Process [_ {:ui/keys [process-router new-proposal-modal]}]
 {:query [::process/slug 
          {:ui/process-router (comp/get-query ProcessRouter)}
          {:ui/new-proposal-modal (comp/get-query ProposalModal)}]
  :ident ::process/slug
  :route-segment ["decision" ::process/slug]}
 (comp/fragment
   (dom/a {:href (str "/decision/" slug "/home")} "Back to home!")
   (ui-global-add-proposal-to-process-modal new-proposal-modal)
   (ui-process-router process-router)))
I change-route! based on the URL, when it changes. I extract all params from the route-segments and add them as extra-params to change-route! . For example: The site navigates to /decision/my-test-decision/proposals , I call (dr/change-route! app ["decision" "my-test-decision" "proposals"] {::process/slug "my-test-decision"}) . This way the router targets in ProcessRouter get the slug as well.

tony.kay17:01:36

Process is not a process. Use the info out of the child to know your slug.

tony.kay17:01:02

I would call is something different…just because you need to know something about your children does not mean you are your children

Björn Ebbinghaus10:01:58

What exactly do you mean with “Use the info out of the child to know your slug.“? Notice, that I shortened the query for Process and it could query for ::process/title as well. In my case even the ui-global-add-proposal-to-process-modal has the ident: ::process/slug because it needs access to ::process/all-proposals (As a proposal can have another proposal as its parent) and ::process/slug

tony.kay17:01:40

Your choices are: • Reorganize things so you don’t end up with deferred routers on the same ident. • Don’t use dynamic routers.

tony.kay17:01:18

“has the ident: `::process/slug` because it needs access to `::process/all-proposals` ” is an invented constraint by you not Fulcro or Pathom

tony.kay17:01:39

“I shortened the query for `Process`  and it could query for `::process/title` ” But IS it a Process? It has to, as a parent, know about children. composition recommendations are that children are unaware of parents. In a UI parents are very very often forced to have knowledge of their children. You mix it into the query and render body. You’re stuck with it.

tony.kay17:01:20

don’t confuse the concerns of the URL bar with the data model of your UI with the data model in your back-end.