如何将Ecto变换集设计为真正的before_save回调

Nim*_*mir 1 elixir ecto phoenix-framework

我有一个简单的before_save转换,并了解到phoenix使用Ecto changest来完成这项任务.

我的Stage模型有一个position属性,默认为current maximum + 1如此尝试实现如下:

舞台模型:

  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, @required_fields, @optional_fields)
    |> validate_required([:name])
    |> set_position
  end

  defp set_position(current_changeset) do
    # get current max position from db
    max_position = Repo.one(
      from s in Stage,
      select: fragment("COALESCE(MAX(?),0)", s.position)
    )

    case current_changeset do
      %Ecto.Changeset{valid?: true} -> 
        put_change(current_changeset, :position, max_position+1)
      _ ->
        current_changeset
    end
  end
Run Code Online (Sandbox Code Playgroud)

在逐个插入记录时工作正常但在批量插入时失败; 例如在下面的seed文件中.

种子

alias MyApp.{Repo, Post}

[
  %{name: "Requirements"},
  %{name: "Quotation"},
  %{name: "Development"},
  %{name: "Closing"}
] 
|> Enum.map(&Post.changeset(%Post{}, &1)) 
|> Enum.each(&Repo.insert!(&1))
Run Code Online (Sandbox Code Playgroud)

预期/当前行为:

如果说当前最大位置7,对于上面所有插入的4个记录,位置将分别设置为8而不是8,9,10,11!那是因为第一个管道将准备所有的更换然后插入它们!

我播种的方式错了吗?还是变革集?我怎样才能重新设计这个,所以无论我如何进行插入,行为都是一样的?任何反馈,以改善我如何做到这一点表示赞赏!

Dog*_*ert 5

您可以使用Ecto.Changeset.prepare_changes/2在该变更集的数据库事务中运行任意计算.你的set_position/1函数有正确的参数/返回值(changeset - > changeset),所以你只需要改变:

|> set_position
Run Code Online (Sandbox Code Playgroud)

|> prepare_changes(&set_position/1)
Run Code Online (Sandbox Code Playgroud)

set_position现在在相同的事务中,只是你的帖子被插入之前执行的,而不是同时创建变更被执行.