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
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.kay understood, I just wanted to throw the question out there, in case someone would recommend a good solution that I can start with.
@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.
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
@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.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.
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).
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.
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.
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.
and the error checking doesnât care about them
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.
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.
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.
It doesnât matter so much whether it works or not. Itâs not really worth my time right now, but curiosity grabbed me.
I saw the specific line and though: âGreat, I am not the first one to try thisâ đ
@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}}
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.
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
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.
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])
To test if your thing works, just make sure get-query and get-ident (both arities) work on it.
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.
(state would then have to be able to contain card 1 vectors as idents, which would confuse all manner of things)
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!
which you actually need to use anyway, since parameters (by their nature) vary
My general reasoning was: You need to pre-set bindings and parameters before they make sense, so why not just use set-query?
but because all that introduces the need for logic in the same code points as load, why not just use load params?
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.
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)
Oops didnât mean to pin that đ
the props of the child flow through the parent
yeah, I figured đ
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
ah I see.. so on any updates to the child just make sure those transactions are also updating the parent ident
(transact! this [(update-field âŠ) :parent-prop])
yep
got it, thanks
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
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
I guess I just donât see why editing one field would make the rest invalid
âedit one of themâ is never and has never been the trigger
mark-complete is what changes that
and it can be called for a field or the whole form, but it is in your control
I see.. so what is the recommendation in this scenario? The onChange is causing the whole form to be invalid at this point
add the field argument to mark complete
Hm. I thought that could be as simple as remove any checks if an ident is exactly card. 2. Ok. Thanks for your reply.
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.
what are your route segments?
@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â.
What are you doing that is causing your children to have the same idents as the parent?
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
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-segment
s 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.Process
is not a process. Use the info out of the child to know your slug.
I would call is something differentâŠjust because you need to know something about your children does not mean you are your children
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
Your choices are: âą Reorganize things so you donât end up with deferred routers on the same ident. âą Donât use dynamic routers.
âhas the ident: `::process/slug` because it needs access to `::process/all-proposals` â is an invented constraint by you not Fulcro or Pathom
â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.
donât confuse the concerns of the URL bar with the data model of your UI with the data model in your back-end.
When you say:
> It has to [..] know about children
and
> parents are very very often forced to have knowledge of their children
Do you mean that in a sense, that they are just aware of their existence and/or relationship?
Or do you mean that in a practical sense that they know about the query and props a child has?
---------------------------------------------
So I renamed the component to ProcessContext
and pulled out the Process
related stuff into a new component ProcessOutline
and changed the ident of the component. Is that what you had in mind?
(defsc ProcessOutline [_ _]
{:query [::process/slug ::process/title ::process/description]
:ident ::process/slug}
(dom/a {:href (str "/decision/" slug "/home")} "Back to home!"))
(def ui-process-outline (comp/factory ProcessOutline)
(defsc ProcessContext [_ {:ui/keys [process-router new-proposal-modal] :keys [process-outline]}]
{:query [::process/slug
{:process-outline (comp/get-query ProcessOutline)}
{:ui/process-router (comp/get-query ProcessRouter)}
{:ui/new-proposal-modal (comp/get-query ProposalModal)}]
:ident [:process-context ::process/slug]
:route-segment ["decision" ::process/slug]
:will-enter (fn [app {slug ::process/slug}]
(let [ident (comp/get-ident ProcessContext {::process/slug slug})]
(dr/route-deferred ident
(fn []
(df/load! app [::process/slug slug] ProcessOutline
{:target (targeting/replace-at (conj ident :process-outline))})
(mrg/merge-component! app ProcessContext (comp/get-initial-state ProcessContext {:slug slug}))
(dr/target-ready! app ident)))))}
(comp/fragment
(ui-process-outline process-outline)
(ui-global-add-proposal-to-process-modal new-proposal-modal)
(ui-process-router process-router)))
Itâs your program, Iâm not sure what youâre thinking. I was just expressing that it makes little sense to me for the identity of a specific parent to be identical to a specific child of that same parent. âI am my own grandpaâ is not a good structure. If you have a desire to split duties of rendering that can be done with functions. If the parent is being given the slug because you want to show it in the parent, why not just pull the slug out of the child props?
Your idea of using an alt table is sorta âcustomized denormalized normalizationââŠ
HmâŠ
I thought using the props of a child was kind of bad, because it is misusing the colocation aspect. What happens, when the query of your child changes? That would break the parent.
Thatâs the motivation behind my recent question about an âanonymous componentâ.
> Your idea of using an alt table is sorta âcustomized denormalized normalizationââŠ
How would I compose it otherwise? I donât want to load!
:process-context
as it isnât the concern of the backend to know about this.
I have to do it manual, right?
Ok, no, not using the props of children is stupid... You need to work with them when you do any kind of filtering or sorting, or similar. đ
Yep. Parents always need to know something about their children. It is the reverse that is more of a problem, because the parent context is usually a mystery, so to get things from a parent we always provide things like callbacks and published API of the child so the parent can âblack boxâ discover it. Itâs couping, but itâs one-way and well-defined. The parent also needs to know the query of the child (it has to compose it in). That is a grey area because you are right, it doesnât need to know the content of the queryâŠunless it is a TodoList where âcheck allâ state comes from looking at the children, or your case, where you want to know the slug. It is just the way it is. You donât want to define a new prop or query to track these kinds of things. The practical cost of the parent knowing something about the child, as youâre realizing, is trivial, worth it, and standard React (you look at the child API to find callbacks, why not consider the query a part of the âpublished APIâ).
[â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.
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
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
But how do I initialise the inner router?
It canât be initialised on app startup because of the parameter.
sure it can, you can initialize state and set root with set-root!
, then use dr/route-to!
then mount w/o initialize
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)