Elixir State machine thin layer for structs
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 3 年前 | ||
| 5 年前 | ||
| 3 年前 | ||
| 3 年前 | ||
| 3 年前 | ||
| 7 年前 | ||
| 8 年前 | ||
| 5 年前 | ||
| 5 年前 | ||
| 3 年前 | ||
| 8 年前 | ||
| 5 年前 | ||
| 5 年前 | ||
| 3 年前 | ||
| 8 年前 | ||
| 3 年前 | ||
| 5 年前 | ||
| 8 年前 | ||
| 3 年前 |
机械装置(Machinery)

Machinery 是一个轻量级的 Elixir 状态机库,内置了 Phoenix 集成功能。它提供了一个简单的声明状态的 DSL,并支持条件语句和回调。
目录
安装
在 mix.exs 的依赖列表中添加 :machinery:
def deps do
[
{:machinery, "~> 1.1.0"}
]
end
在你想应用状态机的模块上创建一个名为 state(或其他自定义名称)的字段,并确保它被包含在你的 defstruct 中。
如果使用 Phoenix 模型,将其作为 string 添加到模式中并在 changeset/2 函数中包括它:
defmodule YourProject.User do
schema "users" do
# ...
field :state, :string
# ...
end
def changeset(%User{} = user, attrs) do
#...
|> cast(attrs, [:state])
#...
end
end
声明状态
为您的状态机逻辑创建一个独立的模块。例如,如果你想向 User 模型添加状态机,创建一个 UserStateMachine 模块。
然后在这个新模块中导入 Machinery 并以参数形式声明状态。
Machinery 需要一个关键字参数作为输入,键分别为 field、states 和 transitions。
field: 表示状态字段名的原子(默认值是state)states: 代表每个状态的字符串列表。transitions: 对每个状态及其允许的下一个状态(或状态集)的映射。
示例
defmodule YourProject.UserStateMachine do
use Machinery,
field: :custom_state_name, # 可选,默认值是 `:field`
states: ["created", "partial", "completed", "canceled"],
transitions: %{
"created" => ["partial", "completed"],
"partial" => "completed",
"*" => "canceled"
}
end
你可以使用通配符 "*" 来声明从任意状态到特定状态的转换。
改变状态
通过调用 Machinery.transition_to/3 或 Machinery.transition_to/4 将结构体转换到另一个状态。
Machinery.transition_to/3 或 Machinery.transition_to/4
接受以下参数:
struct: 要转换状态的结构体。state_machine_module: 包含状态机逻辑的模块,即导入 Machinery 的模块。next_event: 字符串,表示结构体要转换到的下一个状态。- (可选)
extra_metadata: 一个映射,可以包含你在由状态变化触发的任何状态机函数中可能需要访问的额外数据。
Machinery.transition_to(your_struct, YourStateMachine, "next_state")
# {:ok, updated_struct}
# 或者
Machinery.transition_to(your_struct, YourStateMachine, "next_state", %{extra: "metadata"})
# {:ok, updated_struct}
示例
user = Accounts.get_user!(1)
{:ok, updated_user} = Machinery.transition_to(user, UserStateMachine, "completed")
持久化状态
要在状态转换后持久化结构体,你需要在状态机模块中声明一个 persist/2 或 /3 (如果你想访问传递给 transition_to/4 的元数据)函数。
此函数将接收未修改的 struct 作为第一个参数,以及一个表示下一个状态的 string 作为第二个参数。
你的 persist/2 或 persist/3 应该始终返回更新后的结构体。
示例
defmodule YourProject.UserStateMachine do
alias YourProject.Accounts
use Machinery,
states: ["created", "completed"],
transitions: %{"created" => "completed"}
# 你可以添加一个可选的第三个参数来访问传递给 `transition_to/4` 的元数据。
def persist(struct, next_state) do
# 使用新的状态在数据库中更新用户。
{:ok, user} = Accounts.update_user(struct, %{state: next_stated})
# `persist` 总应返回更新后的结构体
user
end
end
记录转换
为了记录转换,Machinery 提供了一个 log_transition/2 或 /3 (如果你想访问传递给 transition_to/4 的元数据)回调,该回调会在执行 persist 函数后对每次转换调用。
这个函数接收未修改的 struct 作为第一个参数,一个表示下一个状态的 string 作为第二个参数。
log_transition/2 或 log_transition/3 应该始终返回结构体。
示例
defmodule YourProject.UserStateMachine do
alias YourProject.Accounts
use Machinery,
states: ["created", "completed"],
transitions: %{"created" => "completed"}
# 你可以添加一个可选的第三个参数来访问传递给 `transition_to/4` 的元数据。
def log_transition(struct, _next_state) do
# 在这里记录转换。
# ...
# `log_transition` 总应返回结构体
struct
end
end
守护函数
通过在状态机模块中添加 guard_transition/2 或 /3 (如果你想访问传递给 transition_to/4 的元数据)函数签名来创建守护条件。
这个函数会接收两个参数:struct 和一个它将转换到的状态的 string。
使用第二个参数对想要守护的特定状态进行模式匹配。
# 第二个参数用于模式匹配并保护对该状态的转换。
#
# 你可以添加一个可选的第三个参数来访问传递给 `transition_to/4` 的元数据。
def guard_transition(struct, "guarded_state") do
# 你的守护逻辑在这里
end
守护条件只有在返回的不是 {:error, "cause"} 元组时才会允许转换:
{:error, "cause"}: 不允许转换。_(其他任何东西): 守护条件将允许转换。
示例
defmodule YourProject.UserStateMachine do
use Machinery,
states: ["created", "completed"],
transitions: %{"created" => "completed"}
# 守护向 "completed" 状态的转换。
def guard_transition(struct, "completed") do
if Map.get(struct, :missing_fields) == true do
{:error, "There are missing fields"}
end
end
end
当试图过渡被其守护条件阻止的结构体时,你会得到以下返回结果:
blocked_struct = %TestStruct{state: "created", missing_fields: true}
Machinery.transition_to(blocked_struct, TestStateMachineWithGuard, "completed")
# {:error, "There are missing fields"}
前置与后置回调函数
您还可以使用前置和后置回调来处理特定状态转换时的预期副作用和响应。
您可以声明 before_transition/2 或 /3(如果需要访问在 transition_to/4 中传递的元数据),
以及 after_transition/2 或 /3(如果需要访问在 transition_to/4 中传递的元数据),根据所需匹配的状态模式。
前置和后置回调函数应返回结构体。
# 前置和后置回调函数应该返回结构体。
# 您可以添加一个可选的第三个参数以接收额外的元数据。
def before_transition(struct, "state"), do: struct
def after_transition(struct, "state"), do: struct
示例
defmodule YourProject.UserStateMachine do
use Machinery,
states: ["created", "partial", "completed"],
transitions: %{
"created" => ["partial", "completed"],
"partial" => "completed"
}
def before_transition(struct, "partial") do
# ... 执行所需的全局副作用操作
struct
end
def after_transition(struct, "completed") do
# ... 执行所需的全局副作用操作
struct
end
end
版权与许可
版权所有 © 2016 João M. D. Moura
本软件遵循 Apache 许可证 2.0 版("许可证")授权; 除非遵守该许可证,否则您不得使用此文件。 您可以在以下链接获取许可证的副本:
http://www.apache.org/licenses/LICENSE-2.0
除非适用法律要求或书面同意,根据许可证分发的软件均按“原样”基础提供, 不附带任何形式的明示或暗示保证或条件。 请参照许可证了解特定的权限和限制规定。
项目介绍
Elixir State machine thin layer for structs