# `GuardedStruct.AshResource.Change`
[🔗](https://github.com/mishka-group/guarded_struct/blob/v0.1.4/lib/guarded_struct/ash_resource/change.ex#L1)

An `Ash.Resource.Change` module that plugs `__guarded_change__/1` into the
Ash changeset pipeline.

## Usage

### Manual wiring

    defmodule MyApp.User do
      use Ash.Resource, extensions: [GuardedStruct.AshResource]

      guardedstruct do
        field :email, :string, derives: "sanitize(trim, downcase) validate(email_r)"
      end

      changes do
        change GuardedStruct.AshResource.Change
      end
    end

### Auto-wiring

Set `auto_wire: true` on the `guardedstruct` section and the change is
injected automatically — no `changes do ... end` block needed.

## Atomic mode

`atomic/3` runs the GuardedStruct pipeline in Elixir on the plain
literal values Ash placed in `changeset.attributes` and
`changeset.atomics`, then returns `{:atomic, sanitized_map}` — the
shape Ash uses to substitute pre-computed values into the single SQL
statement. The action stays atomic (one UPDATE, no extra round-trip)
and the persisted value is the sanitized one.

This means sanitize / validate / derive / `auto:` MFAs / custom
`Derive.Extension` ops all work in atomic mode. The only blocker is
when the user explicitly provides an `Ash.Expr` for a field via
`Ash.Changeset.atomic_update/3`:

    Ash.Changeset.atomic_update(record, :counter, expr(counter + 1))

In that case we can't sanitize a value we won't know until the SQL
evaluates, so `atomic/3` returns `{:not_atomic, reason}` and Ash
falls back to the imperative path. This is rare in practice — 99% of
changesets pass plain literals.

No `require_atomic? false` flag is needed on update / destroy actions.

## Bulk operations

    Ash.bulk_create(input_list, MyApp.User, :create,
      return_records?: true,
      return_errors?: true
    )

`Ash.bulk_update/3` also works — `strategy: :atomic` (the default) uses
our atomic pattern; `strategy: :stream` uses the imperative `change/3`
path. Both produce identical results.

# `after_action?`

# `after_batch?`

# `around_action?`

# `atomic`

The atomic-mode callback.

  * If any field's atomic value is an `Ash.Expr`, bail with
    `{:not_atomic, reason}` — we can't transform a value we won't
    know until the SQL evaluates.
  * Otherwise, run the GuardedStruct pipeline on the combined
    `attributes` + `atomics` literals and return
    `{:atomic, sanitized_map}` for Ash to substitute into the
    single-statement UPDATE.

# `atomic?`

# `batch_change`

Bulk-action entry. Maps `change/3` over each changeset.

# `batch_change?`

# `before_action?`

# `before_batch?`

# `change`

The `Ash.Resource.Change` callback for the non-atomic / regular-change
path. Runs the GuardedStruct pipeline on `changeset.attributes` and
applies the transformed values back, or adds errors.

# `has_after_action?`

# `has_after_batch?`

# `has_around_action?`

# `has_atomic?`

# `has_batch_change?`

# `has_before_action?`

# `has_before_batch?`

# `has_change?`

# `has_init?`

# `has_validate?`

# `validate?`

---

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