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

时间:2017-03-30 11:02:59

标签: elixir phoenix-framework ecto

我有一个简单的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

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

种子

alias MyApp.{Repo, Post}

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

预期/当前行为:

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

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

1 个答案:

答案 0 :(得分:5)

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

|> set_position

|> prepare_changes(&set_position/1)

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