我想根据现有数据更新条目中的字段。
我可以在变更集管道中定义方法吗?还是我需要先更新数据,然后应用变更集?
例如,我有一个UUID
用户。如果长度未达到8,我需要使用额外的“ 0”更新字段。
“ 123” =>“ 00000123”
我知道我们总是可以生成新数据的地图,然后传递给变更集:
user = Repo.get(User, 1)
new_uuid = "00000#{user.uuid}"
user
|> User.changeset(%{uuid: new_uuid})
|> Repo.update()
但是我在想是否可以在变更集管道内部进行逻辑处理?如果可能的话,如何编写代码?
user = Repo.get(User, 1)
user
|> <modify the data>
|> Repo.update()
答案 0 :(得分:0)
没有魔术。 Ecto.Changeset
只是一个简单的结构,所有辅助函数都可以对其进行简单地修改,可能会增加错误和/或更改数据。
也就是说,您可以使用一种@spec
来实现该功能
@spec my_fun(Ecto.Changeset.t(), ...) :: Ecto.Changeset.t()
并将其应用到管道中的任何位置(在设置UUID
值之后。)需要使用此签名才能将该函数插入管道中。
有点像:
@spec prepend_zeroes_to_uuid(Ecto.Changeset.t()) :: Ecto.Changeset.t()
def prepend_zeroes_to_uuid(%{uuid: uuid} = changeset)
when is_binary(uuid) and byte_size(uuid) >= 8, do: changeset
def prepend_zeroes_to_uuid(%{uuid: uuid} = changeset)
when is_binary(uuid) do
%{changeset | uuid: String.pad_leading(uuid, 8, "0")}
end
def prepend_zeroes_to_uuid(changeset), do: add_error(...)
并将其包含到管道中。
user = Repo.get(User, 1)
user
|> changeset ...
...
|> prepend_zeroes_to_uuid()
|> Repo.update()
要直接更新User
模式,可以对它应用相同的功能。
@spec prepend_zeroes_to_uuid(User.t()) :: User.t()
其余部分保持不变。
答案 1 :(得分:0)
经过研究,我找到了解决问题的方法,但是我将保留此问题,以查看是否有更好的答案。
Changeset
设计用于新数据或更改的数据。如果只是从params中获取数据,或者已经有新数据,则可以使用changeset来进行验证并更新数据,然后再推送到数据库。
但是,它不能用于验证/更新现有数据(即更改了有效规则,我们需要更新现有数据)。那是我的问题。
因此,我要做的是从数据库中获取数据,更改数据,然后推送到变更集并更新数据库,而不是使用变更集来获取有效数据。
对于我的示例,我们将uuid
更新为new_uuid
,并将其也传递给变更集。
user = Repo.get(User, 1)
new_uuid = "00000#{user.uuid}"
user
|> User.changeset(%{uuid: new_uuid})
|> Repo.update()
我不确定此解决方案是否是最佳做法,如果有人有更好的解决方案来更新现有数据,请发表您的回答,谢谢。
答案 2 :(得分:0)
@Alekseis答案的问题是语法:
%{changeset | uuid: String.pad_leading(uuid, 8, "0")}
首先,所有更改将保存在changeset.changes
中,例如changeset.changes.uuid
。但是只有当该属性实际发生更改时,它们才存在。所以写
%{changeset.changes | uuid: String.pad_leading(uuid, 8, "0")}
对我们无济于事,因为仅当键已存在于地图中时,此语法才起作用。我们可以做这样的事情:
Map.put(changeset, :uuid, String.pad_leading(uuid, 8, "0")
但是我建议使用Ecto.Changeset
中的适当函数,因为变更集可能只是一个结构,但是围绕它有很多逻辑。我们的朋友是:https://hexdocs.pm/ecto/Ecto.Changeset.html#put_change/3
所以让我们重写解决方案:
@spec prepend_zeroes_to_uuid(Ecto.Changeset.t()) :: Ecto.Changeset.t()
# There are changes on uuid but it already is 8 characters long
def prepend_zeroes_to_uuid(%{changes: %{uuid: uuid}} = changeset)
when is_binary(uuid) and byte_size(uuid) >= 8, do: changeset
# There are changes on uuid and it needs padding
def prepend_zeroes_to_uuid(%{changes: %{uuid: uuid}} = changeset) when is_binary(uuid) do
put_change(changeset, :uuid, String.pad_leading(uuid, 8, "0"))
end
# There are no changes on uuid
def prepend_zeroes_to_uuid(changeset), do: changeset
# Now in your pipeline:
user
|> User.changeset(%{uuid: new_uuid})
|> prepend_zeroes_to_uuid()
|> Repo.update()