如何在将Ecto变更集插入仓库之前对其进行修改?

时间:2016-06-10 19:14:43

标签: elixir phoenix-framework ecto

我是凤凰城/ Elixir的新手,我正试图绕过变更集。

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

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

我的用例如下:

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

由于不变性约束,我甚至不确定是否可以直接修改变更集,但我可以创建另一个变更集以插入仓库。

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

编辑以下评论: 我正在寻找类似的东西:

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

1 个答案:

答案 0 :(得分:11)

我相信你要找的是一个自定义的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返回该类型的值,并{{1}可以将该类型的值读取到您想要的任何内部表示。一种常见的模式是为内部表示定义一个结构,以便您可以自己实现Poison的def load甚至可以返回一个简单的字符串。一个示例可能是在json中编码为to_json的LatLng类型,在postgres中存储为某种GIS类型,但是具有类似12.12345N,123.12345W的结构,可以让您在elixir中进行一些简单的数学运算。 DateTime格式的工作方式非常类似(有一个elixir结构,db驱动程序的元组格式和json的ISO格式)。

我认为这对密码字段非常有用,顺便说一句。您可以压缩JSON表示,使用结构来表示算法,算法的参数将哈希与盐分开,或者其他任何使生活变得容易的事情。在您的代码中,要更新密码,只需%LatLng{lat: 12.12345, lng: -123.12345}

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