如何更新Ecto /凤凰城中的现有数据?

时间:2019-08-26 05:21:22

标签: elixir phoenix-framework ecto

我想根据现有数据更新条目中的字段。

我可以在变更集管道中定义方法吗?还是我需要先更新数据,然后应用变更集?

例如,我有一个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()

3 个答案:

答案 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()