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

How to design an Ecto changeset as a true before_save callback

我进行了简单的 before_save 转换,了解到 phoenix 使用 Ecto changest 完成此任务。

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

舞台模特:

  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

逐条插入记录正常,批量插入失败;例如在下面的 seed 文件中。

种子

alias MyApp.{Repo, Post}

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

Expected/Current 行为:

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

是我播种方式不对吗?还是变更集?我如何重新设计它,以便无论我如何插入,行为都是相同的?感谢任何改进我的工作方式的反馈!

您可以在该变更集的数据库事务中使用 Ecto.Changeset.prepare_changes/2 到 运行 任意计算。您的 set_position/1 函数具有正确的 argument/return 值(变更集 -> 变更集),因此您只需更改:

|> set_position

|> prepare_changes(&set_position/1)

set_position 现在将在插入 Post 之前在同一事务中执行,而不是在创建变更集时执行。