lib/gearbox/ecto.ex

if Code.ensure_loaded?(Ecto) do
  defmodule Gearbox.Ecto do
    @moduledoc """
    Ecto support module for Gearbox.

    This allows creation of Ecto changeset based on the result of the transition.
    """

    import Gearbox, only: [validate_transition: 3]

    @doc """
    Creates a changeset based on the transition outcome of an Ecto struct.

    Returns a tuple containing a changeset:
      - `{:ok, changeset}` with the transitioned field if the transition can be made
      - `{:error, error_changeset}` with an error populated if transition cannot be made.
    """
    @spec transition_changeset(struct :: struct, machine :: any, next_state :: Gearbox.state()) ::
            {:ok, struct | map} | {:error, Ecto.Changeset.t()}
    def transition_changeset(struct, machine, next_state) do
      validation_struct = maybe_apply_changeset_changes(struct)

      case validate_transition(validation_struct, machine, next_state) do
        {:ok, nil} ->
          changeset = Ecto.Changeset.change(struct, %{machine.__machine_field__ => next_state})
          {:ok, changeset}

        {:error, reason} ->
          error_changeset =
            struct
            |> struct()
            |> Ecto.Changeset.change()
            |> Ecto.Changeset.add_error(machine.__machine_field__, reason)

          {:error, error_changeset}
      end
    end

    defp maybe_apply_changeset_changes(%Ecto.Changeset{} = changeset),
      do: Ecto.Changeset.apply_changes(changeset)

    defp maybe_apply_changeset_changes(struct), do: struct
  end
end