【问题标题】:How to modify an Ecto changeset before inserting it into the repo?如何在将 Ecto 变更集插入到 repo 之前对其进行修改?
【发布时间】:2016-10-11 20:37:44
【问题描述】:

我对 Phoenix/Elixir 真的很陌生,我正在努力解决变更集。

我了解它包含一组用于创建或更新模型的更改。

我想知道的是在将更改推送到数据库之前是否以及如何修改更改。

我的用例如下:

  • 我有一个允许人们在数据库中创建新艺术家的表单。
  • 在这个表格中有一个专业领域。
  • 在创建艺术家之前,我想用“,”分割专业字段,以将其存储为字符串数组

由于不变性约束,我什至不确定通过直接修改变更集是否可行,但我可能会创建另一个变更集以插入到 repo 中。

欢迎提出任何建议,并毫不犹豫地指出我可能正在做的不良做法或愚蠢的事情!

编辑以下评论: 我在看类似的东西:

defp put_specialty_array(changeset) do
  case changeset do
    %Ecto.Changeset{valid?: true, changes: %{specialty: spec}} ->
      put_change(changeset, :specialty, String.split(spec, ","))
    _ ->
      changeset
  end
end

【问题讨论】:

  • 这是一种类似于散列和存储用户密码的方法。检查"Programming Phoenix" 是如何做到的here。 (特别是registration_changeset 如何调用put_pass_hash
  • 所以我可以这样做:defp put_specialty_array(changeset) do case changeset do %Ecto.Changeset{valid?: true, changes: %{specialty: spec}} -> put_change(changeset, :专业,String.split(spec, ",")) 变更集结束结束?
  • 修正了你的建议
  • 好的,所以它就像我希望的那样工作,但现在如果我在我的模式中保留 :string 我的值 [...] 与 type :string 不匹配,如果我放 {:array, : string} 它不验证表单...
  • 您可以有 2 个不同的属性,其中一个是 virtual 属性,它是用户输入的字符串(类似于我分享的示例中 password 属性是虚拟属性)。

标签: elixir phoenix-framework ecto


【解决方案1】:

我相信您正在寻找的是自定义的 Ecto.Type。我一直这样做,效果很好!它看起来像这样:

defmodule MyApp.Tags do
  @behaviour Ecto.Type
  def type, do: {:array, :string}

  def cast(nil), do: {:ok, nil} # if nil is valid to you
  def cast(str) when is_binary(str) do
    str
    |> String.replace(~r/\s/, "") # remove all whitespace
    |> String.split(",")
    |> cast
  end
  def cast(arr) when is_list(arr) do
    if Enum.all?(arr, &String.valid?/1), do: {:ok, arr}, else: :error
  end
  def cast(_), do: :error

  def dump(val) when is_list(val), do: {:ok, val}
  def dump(_), do: :error

  def load(val) when is_list(val), do: {:ok, val}
  def load(_), do: :error
end

然后在你的迁移中,添加一个类型正确的列

add :tags, {:array, :string} 

最后在您的架构中指定您创建的字段类型。

field :tags, MyApp.Tags

然后您可以将它作为一个字段添加到您的变更集中。如果您的类型转换返回:error,则变更集将出现类似{:tags, ["is invalid"]} 的错误。然后,您不必担心模型或控制器中对字段的任何处理。如果用户为该值发布一个字符串数组或只是一个逗号分隔的字符串,它将起作用。

如果您需要以不同的格式将值保存到数据库中,您只需更改def type 的返回值并确保def dump 返回该类型的值并且def load 可以读取一个值那种类型的任何你想要的内部表示。一种常见的模式是为内部表示定义一个结构,这样您就可以自己实现 Poison 的 to_json,它甚至可以返回一个简单的字符串。一个例子可能是在 json 中编码为 12.12345N,123.12345W 的 LatLng 类型,在 postgres 中存储为某种 GIS 类型,但具有类似 %LatLng{lat: 12.12345, lng: -123.12345} 的结构,可让您在 elixir 中进行一些简单的数学运算。 DateTime 格式的工作原理与此非常相似(有一个用于 elixir 的结构,一个用于 db 驱动程序的元组格式和一个用于 json 的 ISO 格式)。

我认为这对密码字段非常有效,顺便说一句。您可以压缩 JSON 表示,使用结构来表示算法,算法的参数将盐与哈希分开,或者其他任何让生活变得简单的东西。在您的代码中,要更新密码,只需 Ecto.Changeset.change(user, password: "changeme")

我意识到这是一个 6 个月前的问题,您可能已经找到了一些东西,但我最终通过谷歌搜索来到这里,并假设其他人也会这样做。

【讨论】:

  • 你知道有没有办法将当前用户的上下文数据注入到自定义字段中?假设我想使用用户的密码密钥加密/解密该字段并且需要从自定义字段中引用该密钥。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多