Unsorted notes on Fulcro RAD.
Tony advises:
There are two primary ways to do this. If it is a true to one relationship, then you can simply make a resolver from person ID to address ID in pathom and then you can just include address things as columns. The other option, which works for any cardinality, is to use the report option
ro/column-EQL
and write a join that pulls the information you want to format in the column, and then supply a column formatter to do the formatting of the nested data.
Let's assume you have a Person
entity with the ref attribute person/home-address
with the target address/id
. You make a resolver to map :person/id to :address/id (possibly running SQL like select p.addr_id from person p where p.id=?
). Then you can ise ro/columns [person/name ... address/city address/country]
(assuming these attributes are defined and have :address/id
among their ao/identities
). Pathom can now navigate from person/id -> address/id -> address/city etc.
In the other case you would extend the address attribute with a join for the properties you want and add a formatter to display them: (defattr home-address :person/home-address :ref {... ro/column-EQL {:person/home-address [:address/city :address/country]}, ro/column-formatter (fn [_report {:address/keys [city country]} _ _] (str city " " country))})
.
You have a report for an entity that has a to-many :ref
attribute. Normally the value of that will be a vector of idents of the referenced entities. But you want to display something nicer - for example some other prop(s) from the child entity or a complete sub-report with these children. How to do that?
For example let's assume that you have a parent report of all Person
which has the to-many ref attribute :person/children
(with ao/target :person/id
) and want to display also the names of each person's children, i.e. :person/name
.
1) As a stringification of some display-friendly child entity props (leveraging a virtual attribute)
The simplest solution is perhaps to create a virtual (not backed by a DB column) attribute with a resolver that returns a string of the concatenated children props that you want to show in the report. Something like:
(defattr children-names :person/children-names :string
{ao/target :person/id
ao/pc-input #{:person/id}
ao/pc-output [:person/children-names]
ao/pc-resolve (fn [{:keys [parser] :as env} {parent-id :person/id}]
;; the env contains already the instance of the Pathom parser
#?(:clj
{:person/children-names
(-> (parser env [{[:person/id parent-id] [{:person/children [:person/name]}]}])
(get [:person/id parent-id])
:person/children
(->>
(map :person/name)
(str/join ", ")))}))})
You can also display the children entities in a sub report. For that, you want to take full control of the row component by providing your own ro/BodyItem
. This must both specify the complete row query (so you can include {:person/children (comp/get-query YourChildComponent)
) - and RAD will thus ignore :row-query-inclusion
and will not add form links for you - and render the body, where it can delegate to rad.report/render-row
. Inside, you can display the children entities in whatever way you want, e.g. leveraging existing report functionality.
Look at what defsc-report
is doing and how it is generating the default *-Row
BodyItem component.
Tony, 1/2021:
Customize the row [via
ro/BodyItem SomeComponent
, see this example of custom BodyItem] but you may have to switch render styles (the default is table, and list is supported). If you use table, then your element (because of DOM, not Fulcro) must be a table row. If you use list it is more general. NOTE: This is all dependent on the internals of the particular render plugin you use. Write the render body of the defsc-report yourself. All the logic is there, there are helpers in report.cljc for triggering all the logic. Render it exactly the way you want. For a special repeated pattern, it might be work making your own render plugin that you can then reuse across your app.
Do not include the person/children
attribute in the report (as it would override the row query inclusion we are about to add). Add something like ro/row-query-inclusion [{:person/children (comp/get-query YourChildComponent)]
.
Now you have the data but no column to display them. One solution is to specify a custom ::rad.report/row-style ::my-style
and having registered a component for it under :com.fulcrologic.rad/controls ::rad.report/row-style->row-layout
in all-control
. The component would be like the default TableRowLayout
but also display the children.
From Tony:
(defn load-statistics [env]
(let [{:project/keys [id]} (report/current-control-parameters env)]
(uism/load env :local/stats TheStats {:params {:project/id id}
:target (conj (uism/actor->ident env :actor/report) :local/stats)})))
;; Use the normal report state machine, but change the handler that post-processes running the report,
;; and adds an additional load that gets the stats
(defstatemachine report-with-stats
(let [base-machine report/report-machine
handler-path [::uism/states :state/loading ::uism/events :event/loaded ::uism/handler]
base-handler (get-in base-machine handler-path)]
(assoc-in base-machine handler-path
(fn [env]
(-> env
(base-handler)
(load-statistics))))))
;; and then I just added :local/stats to the ro/query inclusion after setting the machine:
;; ...
ro/machine report-with-stats
ro/query-inclusions [{:local/stats (comp/get-query TheStats)}]
And now those stats load when the report finishes loading. Just scan through the state machine definition. UISM is order-independent within a handler, so it's pretty easy just to call the existing handler and then patch in your logic.
See https://github.com/fulcrologic/fulcro-rad/blob/develop/src/main/com/fulcrologic/rad/state_machines/incrementally_loaded_report.cljc and its options. Also see the similar, newer Server-side pagination UISM below.
See https://github.com/fulcrologic/fulcro-rad/blob/develop/src/main/com/fulcrologic/rad/state_machines/server_paginated_report.cljc (use it instead of the default report uism) and its server-paginated-report-options
in order to do server side pagination you have to make it possible to say, in a database-specific manner: “OFFSET 100 LIMIT 10”. It depends on the DB tech, in SQL this is making sure you’re using an index with OFFSET/LIMIT, and in Datomic this is index-pull => this is why Fulcro RAD has no default, db-agnostic solution for this
Consider: You want 5 cols in a report. Which ones are sortable? For every sortable column, is there a secondary sort order? For EACH of those combinations, you have to create an index, and you have to communicate between the client and server as to which of them you decided to support. Do you auto-generate a cross product of every possible column combination as indexes in advance?
what you end up wanting on top of all of this is filtering, which means it gets more expensive. In Datomic index-pull works fine, but if you want to filter on top of that it is a post-step and it makes finding the total potentially expensive. The next-offset is also about filtering. If you asked for next page of size 10, and you get that, the filtering might have caused the index scan to walk 150 things, so your next offset actually grows by 150 instead of just 10. So, in that state machine the assumption is we know we have a lot of results, and we can get a page of them with relative ease, but we need the client to track where we last left off scanning for efficiency (beyond just a page number…we need the real index offset) in SQL this is kind of hidden for you, but unless you’re really careful you can really kill your database with a combo of pagination and filtering done on the server.
Tony, 3/2022, answering a question about RAD reports querying for an entity and a sub-entity:
Crossing relations in a report is not magically solved by any technology…there are too many different things you might want to be doing at a given time. [..] every time you cross a reference then there can be any number of things you might want to do: Filter, Sort, Aggregate and to make matters worse, these decisions compound for every additional reference you cross. Throw in some polymophism (that edge, say :feed/items, can point to an image or a comment) and you now have a need to parameterize the decisions around the parameters of handing the edge. So this is why RAD doesn’t really even try to make a stab at handling edges in reports “for you”. The sheer number and complexity of options would be very hard to get right, and just as hard to understand/use.