I’ve been trying to set up a resource with AshStateMachine and testing it out in AshAdmin.
If anyone has any idea about these questions I would be most grateful.
- Is the
after_transactionchange in the basic state machine example in the documentation functional? - How is AshAdmin supposed to handle errors in the case of illegal state changes anyway?
Regarding admin I merely se the name of the resource shift a little as the empty error message tags are patched in place.
Steps to reproduce
The code, without any Phoenix or admin however, is available in this repository.
Get dependencies and fire up an IEX session.
mix deps.get
iex -S mix
Create a ticket.
iex(1)> ticket = Statepoc.Support.Ticket |> Ash.Changeset.for_create(:create) |> Statepoc.Support.create!()
#Statepoc.Support.Ticket<
__meta__: #Ecto.Schema.Metadata<:loaded>,
id: "61868f6d-1dc0-42d5-92c4-01eee81d6d29",
error: nil,
error_state: nil,
state: :pending,
aggregates: %{},
calculations: %{},
...
>
Now the Ticket resource has a change that will hook in to after_transaction/1 and pass through any successful updates but set the attributes :errorand :error_state if receiving changeset, {:error, error}.
Both paths call IO.inspect/2.
changes do
change after_transaction(fn
changeset, {:ok, result} ->
IO.inspect("Got {:ok, result}", label: "after_transaction")
{:ok, result}
changeset, {:error, error} ->
message = Exception.message(error)
IO.inspect(message, label: "after_transaction")
changeset.data
|> Ash.Changeset.for_update(:error, %{
error: message,
error_state: changeset.data.state
})
|> Statepoc.Support.update()
end),
on: [:update]
end
Now trigger a legal state change and see the IO.inspect/2 output.
iex(2)> ticket = ticket |> Ash.Changeset.for_update(:confirm) |> Statepoc.Support.update!()
after_transaction: "Got {:ok, result}"
#Statepoc.Support.Ticket<
__meta__: #Ecto.Schema.Metadata<:loaded>,
id: "61868f6d-1dc0-42d5-92c4-01eee81d6d29",
error: nil,
error_state: nil,
state: :confirmed,
aggregates: %{},
calculations: %{},
...
>
Great, it says Got {:ok, result} and the state attribute is updated.
Let’s trigger an illegal state change to spice things up.
iex(3)> ticket |> Ash.Changeset.for_update(:package_arrived) |> Statepoc.Support.update()
{:error,
%Ash.Error.Invalid{
errors: [
%AshStateMachine.Errors.NoMatchingTransition{
action: :package_arrived,
target: :arrived,
old_state: :confirmed,
changeset: nil,
query: nil,
error_context: [],
vars: [],
path: [],
stacktrace: #Stacktrace<>,
class: :invalid
}
],
stacktraces?: true,
changeset: #Ash.Changeset<
action_type: :update,
action: :package_arrived,
attributes: %{},
relationships: %{},
errors: [
%AshStateMachine.Errors.NoMatchingTransition{
action: :package_arrived,
target: :arrived,
old_state: :confirmed,
changeset: nil,
query: nil,
error_context: [],
vars: [],
path: [],
stacktrace: #Stacktrace<>,
class: :invalid
}
],
data: #Statepoc.Support.Ticket<
__meta__: #Ecto.Schema.Metadata<:loaded>,
id: "61868f6d-1dc0-42d5-92c4-01eee81d6d29",
error: nil,
error_state: nil,
state: :confirmed,
aggregates: %{},
calculations: %{},
...
>,
context: %{actor: nil, authorize?: false},
valid?: false
>,
query: nil,
error_context: [nil],
vars: [],
path: [],
stacktrace: #Stacktrace<>,
class: :invalid
}}
Great, we correctly get an error. However, the after_transaction doesn’t appear to have been triggered since no message was logged to the terminal.
Querying all Tickets also reveals that the record hasn’t been updated with the error as intended.
iex(4)> require Ash.Query
Ash.Query
iex(5)> Statepoc.Support.Ticket |> Statepoc.Support.read()
{:ok,
[
#Statepoc.Support.Ticket<
__meta__: #Ecto.Schema.Metadata<:loaded>,
id: "61868f6d-1dc0-42d5-92c4-01eee81d6d29",
error: nil,
error_state: nil,
state: :confirmed,
aggregates: %{},
calculations: %{},
...
>
]}
The attributes error and error_state are nil and no logging was triggered.
Do note that I think that the message field passed in the update error changeset in the documentation example should be error to match the resource attributes.






















