Override a builder, handler or loader
v0.0.1
The deepest layer of customization replaces how Gervaz builds state, loads data or handles events. Every sub-module of the runtime is a module you can use, and every callback is overridable, so an override is one function that calls super. This guide overrides a form's submit handler and a table's bulk action handler, then wires them through the DSL. The examples come from the snippet form and the translated tables of Mishka CMS.
Before you begin
- Rule out the lighter layers first: a DSL option, a
renderorformatfunction, a hook, a custom type, an adapter and a template. An override is a module that you maintain across Gervaz upgrades. A hook is usually enough for a rule that belongs to one resource. - Know the pieces. The table runtime has
State,DataLoaderandEventsmodules, and the form runtime has the same three. Each has sub-modules thatusea parent. Overrides DSL lists every key and the module it expects.
Override one callback
use the sub-module you want to change, define only the function that differs, and call super to reach the default. The submit handler of the snippet form reports a rejected save to the page. It runs the default submit first, then looks at what the state holds:
defmodule MishkaCmsCoreWeb.Forms.SnippetSubmit do
use MishkaGervaz.Form.Web.Events.SubmitHandler
def submit(state, params, socket) do
socket = super(state, params, socket)
case failure_message(socket.assigns[:form_state]) do
nil -> socket
message -> tap(socket, fn _socket -> send(self(), {:snippet_error, message}) end)
end
end
endA hook runs inside a LiveComponent, so self() is the parent LiveView, and send/2 reaches it. The same holds in a handler.
The translated tables of Mishka CMS sort the masters of translation groups to the front of the bulk read. They override one function of the bulk action handler and pass everything else to the default:
defmodule MishkaCmsCore.LanguageEditions.BulkActionHandler do
use MishkaGervaz.Table.Web.Events.BulkActionHandler
require Ash.Sort
@impl true
def build_bulk_query(resource, %{archive_status: :archived} = state, filter) do
super(resource, state, filter)
|> Ash.Query.sort([{Ash.Sort.expr_sort(language_group_id == id, :boolean), :desc_nils_first}])
end
def build_bulk_query(resource, state, filter), do: super(resource, state, filter)
endWire the module through the DSL
Gervaz reads the module at run time from the compiled config, so no macro tree is recompiled. Name it in the matching section:
form do
events do
submit MishkaCmsCoreWeb.Forms.SnippetSubmit
end
end
table do
events do
bulk_action MishkaCmsCore.LanguageEditions.BulkActionHandler
end
endA key in data_loader or events replaces one sub-builder, as above. To replace a whole subsystem, name a module in state, or in a form's events. The module has to use the parent (MishkaGervaz.Form.Web.Events, MishkaGervaz.Table.Web.State and so on). Every other key of a state block is then ignored. The module option of a table's events and data_loader, and of a form's data_loader, is accepted by the DSL, but Gervaz does not read it.
events do
module MyApp.Form.CustomEvents
end
events MyApp.Form.CustomEvents
state do
module MyApp.Table.CustomState
endOnly data_loader and events accept the module as a positional argument. state takes it as the module option. In a whole state module, call default_init/3 to reach the default init/3, because super would call your module again.
The use macro takes the same keys as options, which is how you build a reusable bundle:
defmodule MyApp.Table.State do
use MishkaGervaz.Table.Web.State,
column: MyApp.Table.ColumnBuilder,
filter: MyApp.Table.FilterBuilder
endEach default is the .Default submodule of its parent, for example MishkaGervaz.Form.Web.Events.SubmitHandler.Default.
Know what you can carry
- The state struct is fixed. There is no field for your own data. Put read-only configuration on
state.static.configat build time, and stage per-interaction values instate.field_valueson a form. Change state withState.update(state, key: value), and do not rebuild the struct. - Reuse the helpers that exist for override authors:
MishkaGervaz.Table.Web.State.Helpers,MishkaGervaz.Form.Web.State.Helpers,MishkaGervaz.Table.Web.DataLoader.Helpers,MishkaGervaz.Form.Web.DataLoader.HelpersandMishkaGervaz.Table.Web.Events.BulkActionHooks. - Keep sanitization in mind. The sanitization handlers clean the values that reach a query or an action, so call
superwhen you override one.
Choose between a hook and an override
| Hook | Override | |
|---|---|---|
| Lives in | The resource's DSL. | A module. |
| Scope | One resource. | Every resource that wires it. |
| Replaces built-in behavior | Only through override_row_action and override_bulk_action. | Yes, entirely. |
| Cost | A function in the DSL. | A module to maintain across upgrades. |
Reach for an override when the mechanism is wrong for your application and not just the policy of one resource.
Next steps
- Overrides DSL: every key of
state,data_loaderandevents, and the module each one expects. - React to table and form events with hooks: the lighter layer.
- How Gervaz works: where these modules sit.