How domain defaults work

v0.0.1

Most resources in an application share their Ash actions, page sizes, labels and adapter. Gervaz lets you declare those once on the Ash domain, and every resource in the domain inherits them. This page explains which keys a resource inherits, in which order Gervaz settles a conflict, and how the submit buttons and the archive actions merge.

What it is and why

The MishkaGervaz.Domain extension adds a mishka_gervaz block to an Ash domain. It has a table section and a form section that mirror a resource's, plus a navigation section. A resource that sets nothing gets the domain's values, so a new admin resource needs only what is specific to it. The options of the domain block are in Domain DSL.

A table needs a read action, and a form with any field needs a create, an update and a read action. Gervaz reports a missing one when it compiles a table that has a route. Declaring those actions on the domain once is usually the most useful default.

How a conflict is settled

  1. A resource key wins over the same key on the domain. The merge is per key and not per block, so a resource that sets one pagination key still inherits the others.
  2. false is an answer, not an absence. A resource with realtime do enabled false end turns realtime off even when the domain turns it on. A key left out inherits. Name a key you depend on, such as ui_adapter, in a resource that writes its section.
  3. The merge happens in two places. The compile-time transformers copy a domain value into a key the resource left unset. For some sections, such as pagination, realtime, refresh, URL sync and archive, MishkaGervaz.Resource.Info.Table.config/1 finishes the merge when you read the config.

Read the merged result with MishkaGervaz.Resource.Info.Table.config/1 and MishkaGervaz.Resource.Info.Form.config/1. Read the domain's own values with MishkaGervaz.Domain.Info.Table.defaults/1 and MishkaGervaz.Domain.Info.Form.defaults/1. The config/1 function of either module returns the whole domain config, both sides.

What a resource inherits

TableForm
ui_adapter, ui_adapter_optsui_adapter, ui_adapter_opts
actor_key, master_checkactor_key, master_check
actions: read, get, destroyactions: create, update, read
pagination: type, page_size, page_size_options, max_page_sizelayout: navigation, persistence, columns, responsive
realtime: enabled, pubsubtemplate, features
theme: header_class, row_class, border_classtheme: form_class, field_class, label_class, error_class
archive, url_sync, refreshsubmit, merged per button

Everything else is resource-only. Identity, columns, filters, row and bulk actions, groups, steps, uploads, hooks and the override modules belong to the resource. A form's layout mode and its steps are not inherited either.

The domain also holds navigation menu groups. These are domain-wide data and not table defaults. See Domain DSL.

How the submit buttons merge

The form's submit merges one button at a time, so a resource can change a single button.

The resource declaresThe result
No submit blockThe whole domain submit is inherited.
A block with some buttonsThe declared buttons win. A missing button falls back to the domain's.
A button with active falseThat button is dropped, even though the domain defines it.
An empty submit do endThe whole domain submit is inherited, as with no block. To drop a button, use active false on it.

A label falls back from the resource to the domain, and then to a built-in default: "Create", "Save Changes" or "Cancel". position follows the resource, then the domain, then :bottom, and ui follows the resource, then the domain, then nil. Put the three labels on the domain once and declare submit on a resource only where it differs. A mount can also hide the submit row of a create form without touching the DSL. See Mount a table and a form.

How the archive actions merge

Archive applies only to a resource that has AshArchival.Resource. For such a resource, the archive block of the resource wins per key, and a missing key falls back to the domain. If neither side names an action, Gervaz uses a built-in set: {:master_archived, :archived}, {:master_get_archived, :get_archived}, {:master_unarchive, :unarchive} and {:master_permanent_destroy, :permanent_destroy}. enabled false on the resource's block turns archive off for that resource. restricted defaults to false and visible to true.

Example

The tags resource of Mishka Blog declares no actions and no pagination. Its domain supplies them. The resource adds the parts that are its own: its identity and route, columns, filters and its adapter and templates. It overrides the adapter that the domain sets as the default:

elixir
# In the domain (MishkaBlog)
mishka_gervaz do
  table do
    ui_adapter MishkaGervaz.UIAdapters.Tailwind

    pagination do
      type :load_more
      page_size 20
    end
  end
end

# In the resource (MishkaBlog.Tag)
presentation do
  template MishkaBlogWeb.Templates.TagCard
  ui_adapter MishkaCmsCoreWeb.UIAdapters.Admin
end