PhoenixKitCatalogue.Metadata (PhoenixKitCatalogue v0.1.14)

Copy Markdown View Source

Global, code-defined list of metadata fields that catalogue resources (items and catalogues) can opt into.

Resources store chosen values in resource.data["meta"] as a flat map keyed by the definition's :key — e.g. %{"color" => "red"}. Only fields the user has explicitly added appear in that map.

The multilang layer owns the top-level data map (keys like _name, _primary_language, per-language entries) — metadata lives strictly under the "meta" sub-key so the two don't collide.

All fields are text inputs for now — typed inputs (decimal / enum / etc.) can be reintroduced later by adding a :type field to the definition shape and dispatching at render + cast time.

Edit definitions/1 to add/remove fields. Removing a field from the list does not wipe stored values — resources that already hold a value for the removed key will surface it as "Legacy" in the form so the data isn't lost; the user can clear it manually.

Form helpers

Callers (LiveViews) drive the three-phase metadata flow through this module's pure helpers:

  • build_state/2 turns a resource.data blob into %{attached, values} on mount (known keys first in definition order, legacy keys sorted).
  • absorb_params/2 folds the user's latest inputs from the form's "meta" submap into state.values on validate/save.
  • inject_into_data/3 casts values to storage shape and wedges them into params["data"]["meta"] right before hitting the context.

See PhoenixKitCatalogue.Web.ItemFormLive / CatalogueFormLive for the reference wiring — one meta_state assign and three calls.

Summary

Functions

Merges whatever the user currently has typed into the metadata inputs (delivered as params["meta"]) into the state's :values. Unattached keys are ignored so the render doesn't resurrect a row the user just removed.

Builds the initial meta state from a resource's data map.

Normalizes a raw form value for storage: trims whitespace, collapses blanks to nil so callers can drop empty entries from the JSONB map.

Fetches a single definition by key. Returns nil if the key isn't in definitions/1.

The global list of metadata definitions for a given resource type. Order here is the order used for the "Add metadata" dropdown.

Casts the state into its storage shape and wedges it into params["data"]["meta"]. Known-key values go through cast_value/2 (blanks become nil → the key drops). Legacy keys (no current definition) pass through untouched so their data isn't silently nuked by a save — the user clears them explicitly via the × button.

Types

definition()

@type definition() :: %{key: String.t(), label: String.t()}

resource_type()

@type resource_type() :: :item | :catalogue

state()

@type state() :: %{
  attached: [String.t()],
  values: %{required(String.t()) => String.t()}
}

Functions

absorb_params(state, params)

@spec absorb_params(state(), map()) :: state()

Merges whatever the user currently has typed into the metadata inputs (delivered as params["meta"]) into the state's :values. Unattached keys are ignored so the render doesn't resurrect a row the user just removed.

Returns the updated state; leaves the state untouched when the params don't contain a "meta" submap.

build_state(resource_type, data)

@spec build_state(resource_type(), map() | struct() | nil) :: state()

Builds the initial meta state from a resource's data map.

Accepts either the raw resource.data (a map or nil) or a struct with a :data field — both shapes surface in practice because new structs default to %{} but a caller may pass nil if the field is unset. Known keys (those in definitions(resource_type)) land first in their declared order; legacy keys (stored but no longer defined) come after, alphabetized, so the UI can flag them and offer a remove-only action without dropping stored data.

Returns %{attached: [key], values: %{key => stringified_value}}.

Examples

iex> Metadata.build_state(:catalogue, %{"meta" => %{"brand" => "Acme"}})
%{attached: ["brand"], values: %{"brand" => "Acme"}}

iex> Metadata.build_state(:item, nil)
%{attached: [], values: %{}}

cast_value(def, value)

@spec cast_value(definition(), term()) :: String.t() | nil

Normalizes a raw form value for storage: trims whitespace, collapses blanks to nil so callers can drop empty entries from the JSONB map.

definition(resource_type, key)

@spec definition(resource_type(), String.t()) :: definition() | nil

Fetches a single definition by key. Returns nil if the key isn't in definitions/1.

definitions(atom)

@spec definitions(resource_type()) :: [definition()]

The global list of metadata definitions for a given resource type. Order here is the order used for the "Add metadata" dropdown.

The :label values are translated at call time via PhoenixKitWeb.Gettext — do not cache the result across locale changes. The :key is stable (it's the JSONB key) and never translated.

inject_into_data(params, map, resource_type)

@spec inject_into_data(map(), state(), resource_type()) :: map()

Casts the state into its storage shape and wedges it into params["data"]["meta"]. Known-key values go through cast_value/2 (blanks become nil → the key drops). Legacy keys (no current definition) pass through untouched so their data isn't silently nuked by a save — the user clears them explicitly via the × button.