Elixir Ecto 仅在创建时添加计算值
Elixir Ecto adding computed value only on create
在 create/insert 上添加计算值的最佳实践方法是什么?我应该为创建和更新创建一个唯一的变更集吗?
例如,我有一个博客 post 模型,我想创建一个标题 slug 值并存储它。这有点做作,但出于某种原因我只想将其设置为创建而不是更新。我应该做类似下面的事情吗?
defmodule MyBlog.Post do
use MyBlog.Web, :model
schema "posts" do
field :title, :string
field :title_slug, :string
field :content, :text
timestamps
end
@required_fields ~w(
title
content
)
@optional_fields ~w()
def create_changeset(model, params \ :empty) do
changeset(model, params)
|> generate_title_slug
end
defp changeset(model, params \ :empty) do
model
|> cast(params, @required_fields, @optional_fields)
end
defp generate_title_slug(changeset) do
put_change(changeset, :title_slug, __some_slug_generation_code__)
end
def update_changeset(model, params \ :empty) do
changeset(model, params)
end
end
您可以使用 Ecto.Model.Callbacks. In your case, the best callback would be before_insert
,它在验证变更集之后但在将变更集插入 Repo 之前运行:
defmodule MyBlog.Post do
use MyBlog.Web, :model
schema "posts" do
field :title, :string
field :title_slug, :string
field :content, :text
timestamps
end
@required_fields ~w(
title
content
)
@optional_fields ~w()
before_insert :generate_title_slug
def changeset(model, params \ :empty) do
model
|> cast(params, @required_fields, @optional_fields)
end
defp generate_title_slug(changeset) do
put_change(changeset, :title_slug, __some_slug_generation_code__)
end
end
现在您的创建和更新操作都将调用 changeset
我强烈反对回调 - 它们难以测试、引入全局状态、晦涩难懂且难以推理。这也违背了 Elixir 的核心原则之一:"explicit is better than implicit"。
Ecto 的核心团队甚至正在考虑取消回调,或者更改名称并减少它们的暴露程度。在别无他法时,使用回调应该是最后的选择。
为了展示回调的问题之一,让我们想象一下您确实使用回调来解决此问题的场景。现在您正在设计一个管理界面,您不想在其中出现这种行为。你如何解决这个问题?你开始陷入禁用回调的困境,在异常中引入异常,并且有一个难以遵循的多分支条件逻辑。但这是一起解决了一个错误的问题!
对于 Ecto 的架构,不同的变更集方法非常好并且非常自然。
这样您就可以对不同的操作进行不同的验证,并且没有什么是全局的。让我们想想您将如何解决我之前展示的场景中的问题。这非常简单 - 您创建另一个变更集函数!
我见过几次的解决方案是更改 changeset
函数以采用三个参数并在第一个参数的类型上进行模式匹配,例如:
def changeset(action, model, params \ :empty)
def changeset(:create, model, params)
# return create changeset
end
def changeset(:update, model, params)
# return update changeset
end
我不确定哪个更好 - 多个函数或一个函数中的模式匹配。这主要是偏好问题。
这可能对您的实际用例没有帮助,但在 slug 示例中,只保留单个 changeset
函数也可能有意义,但检查是否存在 slug 并且仅当不生成新的。
否则我同意 michalmuskala。尽可能使用单独的变更集函数而不是回调。
在 create/insert 上添加计算值的最佳实践方法是什么?我应该为创建和更新创建一个唯一的变更集吗?
例如,我有一个博客 post 模型,我想创建一个标题 slug 值并存储它。这有点做作,但出于某种原因我只想将其设置为创建而不是更新。我应该做类似下面的事情吗?
defmodule MyBlog.Post do
use MyBlog.Web, :model
schema "posts" do
field :title, :string
field :title_slug, :string
field :content, :text
timestamps
end
@required_fields ~w(
title
content
)
@optional_fields ~w()
def create_changeset(model, params \ :empty) do
changeset(model, params)
|> generate_title_slug
end
defp changeset(model, params \ :empty) do
model
|> cast(params, @required_fields, @optional_fields)
end
defp generate_title_slug(changeset) do
put_change(changeset, :title_slug, __some_slug_generation_code__)
end
def update_changeset(model, params \ :empty) do
changeset(model, params)
end
end
您可以使用 Ecto.Model.Callbacks. In your case, the best callback would be before_insert
,它在验证变更集之后但在将变更集插入 Repo 之前运行:
defmodule MyBlog.Post do
use MyBlog.Web, :model
schema "posts" do
field :title, :string
field :title_slug, :string
field :content, :text
timestamps
end
@required_fields ~w(
title
content
)
@optional_fields ~w()
before_insert :generate_title_slug
def changeset(model, params \ :empty) do
model
|> cast(params, @required_fields, @optional_fields)
end
defp generate_title_slug(changeset) do
put_change(changeset, :title_slug, __some_slug_generation_code__)
end
end
现在您的创建和更新操作都将调用 changeset
我强烈反对回调 - 它们难以测试、引入全局状态、晦涩难懂且难以推理。这也违背了 Elixir 的核心原则之一:"explicit is better than implicit"。
Ecto 的核心团队甚至正在考虑取消回调,或者更改名称并减少它们的暴露程度。在别无他法时,使用回调应该是最后的选择。
为了展示回调的问题之一,让我们想象一下您确实使用回调来解决此问题的场景。现在您正在设计一个管理界面,您不想在其中出现这种行为。你如何解决这个问题?你开始陷入禁用回调的困境,在异常中引入异常,并且有一个难以遵循的多分支条件逻辑。但这是一起解决了一个错误的问题!
对于 Ecto 的架构,不同的变更集方法非常好并且非常自然。 这样您就可以对不同的操作进行不同的验证,并且没有什么是全局的。让我们想想您将如何解决我之前展示的场景中的问题。这非常简单 - 您创建另一个变更集函数!
我见过几次的解决方案是更改 changeset
函数以采用三个参数并在第一个参数的类型上进行模式匹配,例如:
def changeset(action, model, params \ :empty)
def changeset(:create, model, params)
# return create changeset
end
def changeset(:update, model, params)
# return update changeset
end
我不确定哪个更好 - 多个函数或一个函数中的模式匹配。这主要是偏好问题。
这可能对您的实际用例没有帮助,但在 slug 示例中,只保留单个 changeset
函数也可能有意义,但检查是否存在 slug 并且仅当不生成新的。
否则我同意 michalmuskala。尽可能使用单独的变更集函数而不是回调。