# `MishkaGervaz.Form.Web.DataLoader.RelationLoader`
[🔗](https://github.com/mishka-group/mishka_gervaz/blob/v0.0.2/lib/mishka_gervaz/form/web/data_loader/relation_loader.ex#L1)

Loads options for relation/select fields in forms.

Follows the same pattern as `MishkaGervaz.Table.Web.DataLoader.RelationLoader`.

## Modes

- `:static` - Load all options at once (`page: false`)
- `:load_more` - Paginated with load more button
- `:search` - Search with pagination (single select)
- `:search_multi` - Search with pagination (multi select)

## Action Resolution

For master users, tries `:master_read` → `:read_any` → `:read`.
For tenant users, uses `:read` with the user's `site_id` as tenant.

## User Override

    defmodule MyApp.Form.RelationLoader do
      use MishkaGervaz.Form.Web.DataLoader.RelationLoader

      def load_options(field, state, opts) do
        super(field, state, opts)
      end
    end

Top-level helpers (`load_all_options/4`, `load_paginated_options/6`,
`build_options/4`, `prepend_nil_option/2`, `resolve_display_field/2`,
`resolve_load_action/3`, `get_tenant/1`, `maybe_apply_search/2`,
`maybe_apply_custom_load/3`, `resolve_selected_fallback/7`) are public
so user overrides can reuse them — they live outside the `__using__`
macro to avoid per-consumer compile cost.

See `MishkaGervaz.Form.Web.DataLoader`,
`MishkaGervaz.Form.Web.DataLoader.Helpers`,
`MishkaGervaz.Form.Web.State`, and the sibling sub-builders
`RecordLoader`, `TenantResolver`, `HookRunner`. The table-side
counterpart is `MishkaGervaz.Table.Web.DataLoader.RelationLoader`.

# `field_options`

```elixir
@spec field_options(map(), map()) :: list()
```

A resource-less field's own options: its list, its zero-arity function's list, or — for
`fn state -> options end` — what the form state makes of them now.

# `flat_options`

```elixir
@spec flat_options(list()) :: [{String.t(), any()}]
```

The options flattened, a grouped one labelled `"Group · Label"` as it reads once picked.

# `get_display_value`

```elixir
@spec get_display_value(map(), atom() | function(), map() | nil) :: String.t()
```

A record's option label, by its `display_field` as `MishkaGervaz.Helpers.resolve_label/3` reads it:
an attribute, `fn record -> label end`, or `fn record, state -> label end` given the form state. An
attribute the record leaves `nil` reads as the record's id.

    iex> MishkaGervaz.Form.Web.DataLoader.RelationLoader.get_display_value(
    ...>   %{id: "v-1", version_name: nil},
    ...>   :version_name
    ...> )
    "v-1"

# `matching_options`

```elixir
@spec matching_options(list(), String.t()) :: list()
```

The options whose label holds `search_term`. A group `{group_label, [options]}` is kept whole when
its own label holds it, else with the options that do, and left out when none does.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
