# `Ash.Resource.Change`
[🔗](https://github.com/ash-project/ash/blob/v3.34.4/lib/ash/resource/change/change.ex#L5)

The behaviour for an action-specific resource change.

`c:init/1` is defined automatically by `use Ash.Resource.Change`, but can be implemented if you want to validate/transform any
options passed to the module.

The main function is `c:change/3`. It takes the changeset, any options that were provided
when this change was configured on a resource, and the context, which currently only has
the actor.

# `context`

```elixir
@type context() :: Ash.Resource.Change.Context.t()
```

# `ref`

```elixir
@type ref() :: {module(), Keyword.t()} | module()
```

# `t`

```elixir
@type t() :: %Ash.Resource.Change{
  __spark_metadata__: Spark.Dsl.Entity.spark_meta(),
  always_atomic?: term(),
  change: term(),
  description: term(),
  on: term(),
  only_when_valid?: term(),
  where: term()
}
```

# `after_batch`
*optional* 

```elixir
@callback after_batch(
  changesets_and_results :: [{Ash.Changeset.t(), Ash.Resource.Record.t()}],
  opts :: Keyword.t(),
  context :: Ash.Resource.Change.Context.t()
) ::
  :ok
  | Enumerable.t(
      {:ok, Ash.Resource.Record.t()}
      | {:error, Ash.Error.t()}
      | Ash.Notifier.Notification.t()
    )
```

Runs on each batch result after it is dispatched to the data layer.

# `atomic`
*optional* 

```elixir
@callback atomic(
  changeset :: Ash.Changeset.t(),
  opts :: Keyword.t(),
  context :: Ash.Resource.Change.Context.t()
) ::
  {:ok, Ash.Changeset.t()}
  | {:atomic, %{optional(atom()) =&gt; Ash.Expr.t() | {:atomic, Ash.Expr.t()}}}
  | {:atomic, Ash.Changeset.t(), %{optional(atom()) =&gt; Ash.Expr.t()}}
  | {:atomic, Ash.Changeset.t(), %{optional(atom()) =&gt; Ash.Expr.t()},
     [
       {:atomic, involved_fields :: [atom()] | :*,
        condition_expr :: Ash.Expr.t(), error_expr :: Ash.Expr.t()}
     ]}
  | {:atomic, %{optional(atom()) =&gt; Ash.Expr.t()},
     [
       {:atomic, involved_fields :: [atom()] | :*,
        condition_expr :: Ash.Expr.t(), error_expr :: Ash.Expr.t()}
     ]}
  | {:atomic_set, %{optional(atom()) =&gt; Ash.Expr.t() | {:atomic, Ash.Expr.t()}}}
  | [
      atomic: %{optional(atom()) =&gt; Ash.Expr.t() | {:atomic, Ash.Expr.t()}},
      atomic_set: %{optional(atom()) =&gt; Ash.Expr.t() | {:atomic, Ash.Expr.t()}}
    ]
  | {:not_atomic, String.t()}
  | :ok
  | {:error, term()}
```

The atomic version of a change. This is called instead of `c:change/3` when running
atomically. Atomic changes are expressed as maps of attribute names to expressions
that will be evaluated in the data layer.

## Return Values

- `{:atomic, atomics}` - A map of attribute names to expressions for updating existing records.
  Used during the UPDATE phase (for updates or the ON CONFLICT clause of upserts).
  The expression can reference existing values using `atomic_ref(:field)`.

- `{:atomic_set, atomics}` - A map of attribute names to expressions for creating records.
  Used during the INSERT phase of create actions. Cannot use `atomic_ref/1` since there
  is no existing row to reference. For update actions, behaves the same as `{:atomic, ...}`.

- A list containing both `{:atomic, ...}` and `{:atomic_set, ...}` tuples when you need
  to set values for both the INSERT and UPDATE phases (useful for upserts).

- `{:ok, changeset}` - Return a modified changeset (the change was applied in-memory).

- `{:not_atomic, reason}` - Indicates the change cannot run atomically.

- `:ok` - No changes needed.

- `{:error, term}` - An error occurred.

## Examples

    # Simple atomic update (increment counter)
    def atomic(_changeset, _opts, _context) do
      {:atomic, %{counter: expr(counter + 1)}}
    end

    # Atomic set for creates (set timestamp)
    def atomic(_changeset, _opts, _context) do
      {:atomic_set, %{created_at: expr(now())}}
    end

    # Both insert and update values (for upserts)
    def atomic(_changeset, _opts, _context) do
      [
        {:atomic_set, %{created_at: expr(now())}},
        {:atomic, %{updated_at: expr(now())}}
      ]
    end

# `atomic?`

```elixir
@callback atomic?() :: boolean()
```

# `batch_callbacks?`

```elixir
@callback batch_callbacks?(
  changesets_or_query :: [Ash.Changeset.t()] | Ash.Query.t(),
  opts :: Keyword.t(),
  context :: Ash.Resource.Change.Context.t()
) :: boolean()
```

Whether or not batch callbacks should be run (if they are defined). Defaults to `true`.

# `batch_change`
*optional* 

```elixir
@callback batch_change(
  changesets :: [Ash.Changeset.t()],
  opts :: Keyword.t(),
  context :: Ash.Resource.Change.Context.t()
) :: Enumerable.t(Ash.Changeset.t())
```

Replaces `change/3` for batch actions, allowing to optimize changes for bulk actions.

You can define only `batch_change/3`, and it will be used for both single and batch actions.
It cannot, however, be used in place of the `atomic/3` callback.

# `before_batch`
*optional* 

```elixir
@callback before_batch(
  changesets :: [Ash.Changeset.t()],
  opts :: Keyword.t(),
  context :: Ash.Resource.Change.Context.t()
) :: Enumerable.t(Ash.Changeset.t() | Ash.Notifier.Notification.t())
```

Runs on each batch before it is dispatched to the data layer.

# `change`
*optional* 

```elixir
@callback change(
  changeset :: Ash.Changeset.t(),
  opts :: Keyword.t(),
  context :: Ash.Resource.Change.Context.t()
) :: Ash.Changeset.t()
```

# `has_after_batch?`

```elixir
@callback has_after_batch?() :: boolean()
```

# `has_batch_change?`

```elixir
@callback has_batch_change?() :: boolean()
```

# `has_before_batch?`

```elixir
@callback has_before_batch?() :: boolean()
```

# `has_change?`

```elixir
@callback has_change?() :: boolean()
```

# `init`

```elixir
@callback init(opts :: Keyword.t()) :: {:ok, Keyword.t()} | {:error, term()}
```

# `temporal_safe?`
*optional* 

```elixir
@callback temporal_safe?(opts :: Keyword.t()) :: boolean()
```

Whether this change is safe to run as part of an action on a temporal resource.

Every action on a [temporal resource](/documentation/topics/advanced/temporal-resources.md)
runs "as of" a point in time, which may be in the past or the future. A change that runs
there must not assume the write is happening now: it must not read the wall clock (use
`now()` in expressions, or the changeset's `as_of`), must not have side effects that
assume the present, and must perform any reads or nested actions through Ash so that
`as_of` is threaded to them.

Defaults to `false`. Running a change that is not temporal safe on a temporal resource
raises `Ash.Error.Framework.NotTemporalSafe`. Return `true` to declare the change safe,
inspecting `opts` if it is only safe for some configurations.

---

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