Elixir 变更集自定义验证
Elixir changeset custom validation
我在尝试实施自定义变更集验证时遇到了一些麻烦。
我的架构是:
defenum(VersionStateEnum, ["draft", "active"])
schema "versions" do
field :expires_at, :utc_datetime
field :state, VersionStateEnum
end
我要实现的验证是:expires_at 只能在状态为草稿时设置(这对更新也应该有效,我应该无法删除 expires_at 如果状态仍然是草稿)
我尝试了以下方法:
defp validate_expires_at(changeset) do
expires_at = get_change(changeset, :expires_at)
cond do
get_change(changeset, :state) == :draft ->
case expires_at do
nil -> add_error(changeset, :expires_at, "can't be blank when state is draft")
_ -> changeset
end
get_change(changeset, :state) == :active ->
case expires_at do
nil -> changeset
_ -> add_error(changeset, :expires_at, "cannot be set when state is not draft")
end
true ->
changeset
end
end
end
但它并没有真正起作用,因为即使状态是草稿,我也可以将 expires_at 更新为 nil。感谢任何帮助。
编辑 1:
我的变更集:
@required_fields [
:state
]
@optional_fields [:expires_at]
def changeset(model, params \ nil) do
model
|> cast(params, @required_fields ++ @optional_fields)
|> validate_required(@required_fields)
|> validate_expires_at()
end
调用位置:
def create_document(attrs \ %{}) do
%Document{}
|> Document.changeset(attrs)
|> Repo.insert()
end
如果我对你的问题的理解正确,我认为要解决它你应该也考虑给变更集的结构。
因为,按照您的代码的方式,您仅从变更集中的更改中检查 state
,但如果您尝试单独更新 expires_at
,changes
在变更集中将不包括可能已经设置为 "draft"
的 state
,因此,validate_expires_at
函数中的 cond
块将始终匹配 true
,因为该值将是 nil
.
一种解决方法是像这样更新函数:
defp validate_expires_at(changeset) do
state = get_field(changeset, :state)
expires_at = get_change(changeset, :expires_at)
case state do
:draft ->
case expires_at do
nil -> add_error(changeset, :expires_at, "can't be blank when state is draft")
_ -> changeset
end
:active ->
case expires_at do
nil -> changeset
_ -> add_error(changeset, :expires_at, "cannot be set when state is not draft")
end
_ -> changeset
end
end
end
使用 get_field
而不是 get_change
将尝试从更改中获取字段,但如果未更改,则可以从现有结构和函数的其余部分获取应该可以正常工作
Not sure how the atom/String handling works when inserting and retrieving from the DB. You might need to check if state
could be a String when taken from the changeset's data
我在尝试实施自定义变更集验证时遇到了一些麻烦。 我的架构是:
defenum(VersionStateEnum, ["draft", "active"])
schema "versions" do
field :expires_at, :utc_datetime
field :state, VersionStateEnum
end
我要实现的验证是:expires_at 只能在状态为草稿时设置(这对更新也应该有效,我应该无法删除 expires_at 如果状态仍然是草稿) 我尝试了以下方法:
defp validate_expires_at(changeset) do
expires_at = get_change(changeset, :expires_at)
cond do
get_change(changeset, :state) == :draft ->
case expires_at do
nil -> add_error(changeset, :expires_at, "can't be blank when state is draft")
_ -> changeset
end
get_change(changeset, :state) == :active ->
case expires_at do
nil -> changeset
_ -> add_error(changeset, :expires_at, "cannot be set when state is not draft")
end
true ->
changeset
end
end
end
但它并没有真正起作用,因为即使状态是草稿,我也可以将 expires_at 更新为 nil。感谢任何帮助。
编辑 1: 我的变更集:
@required_fields [
:state
]
@optional_fields [:expires_at]
def changeset(model, params \ nil) do
model
|> cast(params, @required_fields ++ @optional_fields)
|> validate_required(@required_fields)
|> validate_expires_at()
end
调用位置:
def create_document(attrs \ %{}) do
%Document{}
|> Document.changeset(attrs)
|> Repo.insert()
end
如果我对你的问题的理解正确,我认为要解决它你应该也考虑给变更集的结构。
因为,按照您的代码的方式,您仅从变更集中的更改中检查 state
,但如果您尝试单独更新 expires_at
,changes
在变更集中将不包括可能已经设置为 "draft"
的 state
,因此,validate_expires_at
函数中的 cond
块将始终匹配 true
,因为该值将是 nil
.
一种解决方法是像这样更新函数:
defp validate_expires_at(changeset) do
state = get_field(changeset, :state)
expires_at = get_change(changeset, :expires_at)
case state do
:draft ->
case expires_at do
nil -> add_error(changeset, :expires_at, "can't be blank when state is draft")
_ -> changeset
end
:active ->
case expires_at do
nil -> changeset
_ -> add_error(changeset, :expires_at, "cannot be set when state is not draft")
end
_ -> changeset
end
end
end
使用 get_field
而不是 get_change
将尝试从更改中获取字段,但如果未更改,则可以从现有结构和函数的其余部分获取应该可以正常工作
Not sure how the atom/String handling works when inserting and retrieving from the DB. You might need to check if
state
could be a String when taken from the changeset's data