docs: rework docs

This commit is contained in:
Zach Daniel 2024-05-05 09:35:11 -04:00
parent a02b6d3a1c
commit 32e2cedf4e
7 changed files with 157 additions and 125 deletions

View file

@ -1,11 +1,12 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug, needs review
assignees: ''
-https://hexdocs.pm/ash_json_api--
-https://hexdocs.pm/ash_state_machine--
**Describe the bug**
A clear and concise description of what the bug is.
@ -16,12 +17,13 @@ A minimal set of resource definitions and calls that can reproduce the bug.
**Expected behavior**
A clear and concise description of what you expected to happen.
** Runtime
- Elixir version
- Erlang version
- OS
- Ash version
- any related extension versions
\*\* Runtime
- Elixir version
- Erlang version
- OS
- Ash version
- any related extension versions
**Additional context**
Add any other context about the problem here.

View file

@ -14,6 +14,12 @@ Welcome! This is the extension for building state machines with [Ash](https://he
- [Getting Started with AshStateMachine](documentation/tutorials/getting-started-with-ash-state-machine.md)
## Topics
- [What is AshStateMachine?](documentation/topics/what-is-ash-state-machine.md)
- [Charts](documentation/topics/charts.md)
- [Working with `Ash.can?`](documentation/topics/working-with-ash-can.md)
## Reference
- [AshStateMachine DSL](documentation/dsls/DSL:-AshStateMachine.md)

View file

@ -0,0 +1,13 @@
# Charts
Run `mix ash_state_machine.generate_flow_charts` to generate flow charts for your resources. See the task documentation for more. Here is an example:
```mermaid
stateDiagram-v2
pending --> confirmed: confirm
confirmed --> on_its_way: begin_delivery
on_its_way --> arrived: package_arrived
on_its_way --> error: error
confirmed --> error: error
pending --> error: error
```

View file

@ -0,0 +1,42 @@
# What is AshStateMachine?
## What is a State Machine?
A state machine is a program who's purpose is to manage an internal "state". The simplest example of a state machine could be a program representing a light switch. A light switch might have two states, "on" and "off". You can transition from "on" to "off", and back.
```mermaid
classDiagram
class Switch {
state on | off
turnOn() off -> on
turnOff() on -> off
}
```
To build state machines with `Ash.Resource`, we use [`AshStateMachine`](https://hexdocs.pm/ash_state_machine).
When we refer to "state machines" in AshStateMachine, we're referring to a specific type of state machine known as a "Finite State Machine".
It is "finite", because there are a statically known list of states that the machine may be in at any time, just like the `Switch` example above.
### Why should we use state machines?
#### Flexible
State machines are a _simple_ and _powerful_ way to represent complex workflows. They are flexible to modifications over time by adding new states, or new transitions between states.
#### Migrateable
State machines typically contain additional data about the state that they are in, or past states that they have been in, and this state must be migrated over time. When representing data as state machines, it becomes simple to do things like "update all `package` records that are in the `pending_shipment` state".
#### Easy to reason about for humans
State machines, when compared to things like workflows, are easy for people to reason about. We have an intuition for things like "the package is currently `on_its_way`, with a `current_location` of New York, New York", or "your package is now `out_for_delivery` with an ETA of 6PM".
#### Compatible with any storage mechanism
Since state machines are backed by simple state, you can often avoid any fancy workflow runners or complex storage mechanisms. You can store them in a database table, a json blob, a CSV file, at the end of the day its just a `:state` field and accompanying additional fields.
## What does AshStateMachine do differently than other implementations?
AshStateMachine is an [`Ash.Resource`](https://hexdocs.pm/ash/Ash.Resource.html) extension, meaning it _enhances a resource_ with state machine capabilities. In `Ash`, all modifications go through [_actions_](actions.html). In accordance with this, `AshStateMachine` offers a DSL for declaring _valid states and transitions_, but does not, itself, _perform_ those transitions. You will use a change called `transition_state/1` in an action to move from one state to the other. For more, check out the [CookBook](https://hexdocs.pm/ash/readme.html#cookbook)

View file

@ -0,0 +1,13 @@
# Working with `Ash.can?`
Using `Ash.can?/3` won't return `false` if a given state machine transition is invalid. This is because `Ash.can?/3` is only concerned with policies, not changes/validations. However, many folks use `Ash.can?/3` in their UI to determine whether a given button/form/etc should be shown. To help with this you can add the following to your resource:
```elixir
policies do
policy always() do
authorize_if AshStateMachine.Checks.ValidNextState
end
end
```
This check is only used in _pre_flight_ authorization checks (i.e calling `Ash.can?/3`), but it will return `true` in all cases when running real authorization checks. This is because the change is validated when you use the `transition_state/1` change and `AshStateMachine.transition_state/2`, and so you would be doing extra work for no reason.

View file

@ -10,126 +10,79 @@ If you haven't already, read the [Ash Getting Started Guide](https://hexdocs.pm/
{:ash_state_machine, "~> 0.2.3-rc.1"}
```
## Add the extension to your resource
```elixir
use Ash.Resource,
extensions: [AshStateMachine]
```
## Add initial states, and a default initial state
```elixir
use Ash.Resource,
extensions: [AshStateMachine]
...
state_machine do
inital_states [:pending]
default_inital_state :pending
end
```
## Add allowed transitions
```elixir
state_machine do
inital_states [:pending]
default_inital_state :pending
transitions do
# `:begin` action can move state from `:pending` to `:started`/`:aborted`
transition :begin, from: :pending, to: [:started, :aborted]
end
end
```
## Use `transition_state` in your actions
### For simple/static state transitions
```elixir
actions do
update :begin do
# for a static state transition
change transition_state(:started)
end
end
```
### For dynamic/conditional state transitions
```elixir
defmodule Start do
use Ash.Resource.Change
def change(changeset, _, _) do
if ready_to_start?(changeset) do
AshStateMachine.transition_state(changeset, :started)
else
AshStateMachine.transition_state(changeset, :aborted)
end
end
end
actions do
update :begin do
# for a dynamic state transition
change Start
end
end
```
## Making a resource into a state machine
The concept of a state machine (in this case a "Finite State Machine"), essentially involves a single `state`, with specified transitions between states. For example, you might have an order state machine with states `[:pending, :on_its_way, :delivered]`. However, you can't go from `:pending` to `:delivered` (probably), and so you want to only allow certain transitions in certain circumstances, i.e `:pending -> :on_its_way -> :delivered`.
This extension's goal is to help you write clear and clean state machines, with all of the extensibility and power of Ash resources and actions.
## A basic state machine
```elixir
defmodule Order do
# leaving out data layer configuration for brevity
use Ash.Resource,
extensions: [AshStateMachine]
state_machine do
initial_states [:pending]
default_initial_state :pending
transitions do
transition :confirm, from: :pending, to: :confirmed
transition :begin_delivery, from: :confirmed, to: :on_its_way
transition :package_arrived, from: :on_its_way, to: :arrived
transition :error, from: [:pending, :confirmed, :on_its_way], to: :error
end
end
actions do
# create sets the state
defaults [:create, :read]
update :confirm do
# accept [...]
# you can change other attributes
# or do anything else an action can normally do
# this transition will be validated according to
# the state machine rules above
change transition_state(:confirmed)
end
update :begin_delivery do
# accept [...]
change transition_state(:on_its_way)
end
update :package_arrived do
# accept [...]
change transition_state(:arrived)
end
update :error do
accept [:error_state, :error]
change transition_state(:error)
end
end
changes do
# any failures should be captured and transitioned to the error state
change after_transaction(fn
changeset, {:ok, result}, _ ->
{:ok, result}
changeset, {:error, error}, _ ->
if changeset.context[:error_handler?] do
{:error, error}
else
changeset.data
|> Ash.Changeset.for_update(:error, %{
error_state: changeset.data.state
})
|> Ash.Changeset.set_context(%{error_handler?: true})
|> Ash.update()
{:error, error}
end
end),
on: [:update]
end
end
attributes do
uuid_primary_key :id
# ...attributes like address/delivery options would go here
attribute :error, :string
attribute :error_state, :string
# :state attribute is added for you by `state_machine`
# however, you can add it yourself, and you will be guided by
# compile errors on what states need to be allowed by your type.
end
end
```
## Adding a state machine policy
Using `Ash.can?/3` won't return `false` if a given state machine transition is invalid. This is because `Ash.can?/3` is only concerned with policies, not changes/validations. However, many folks use `Ash.can?/3` in their UI to determine whether a given button/form/etc should be shown. To help with this you can add the following to your resource:
```elixir
policies do
policy always() do
authorize_if AshStateMachine.Checks.ValidNextState
end
end
```
This check is only used in _pre_flight_ authorization checks (i.e calling `Ash.can?/3`), but it will return `true` in all cases when running real authorization checks. This is because the change is validated when you use the `transition_state/1` change and `AshStateMachine.transition_state/2`, and so you would be doing extra work for no reason.
## Generating Flow Charts
run `mix ash_state_machine.generate_flow_charts` to generate flow charts for your resources. See the task documentation for more. Here is a chart generated from the example above:
```mermaid
stateDiagram-v2
pending --> confirmed: confirm
confirmed --> on_its_way: begin_delivery
on_its_way --> arrived: package_arrived
on_its_way --> error: error
confirmed --> error: error
pending --> error: error
```
## Learning more
- Check out the [DSL documentation](dsl-ashstatemachine.html)
- Check out the `AshStateMachine` module docs.

View file

@ -79,6 +79,9 @@ defmodule AshStateMachine.MixProject do
extras: [
{"README.md", title: "Home"},
"documentation/tutorials/getting-started-with-ash-state-machine.md",
"documentation/topics/what-is-ash-state-machine.md",
"documentation/topics/charts.md",
"documentation/topics/working-with-ash-can.md",
"documentation/dsls/DSL:-AshStateMachine.md"
],
groups_for_extras: [