You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ecto更新时Changeset函数未触发,如何设置仅更新执行的函数?

Ecto更新时触发自定义逻辑的解决方案

你遇到的问题根源在于:Ecto.Changeset.change/2是Ecto原生的基础函数,只会生成最简化的changeset,不会调用你自定义的User.changeset/2这类包含校验、哈希逻辑的函数。要实现更新时触发特定逻辑(比如密码哈希),可以通过以下两种方式解决:

1. 在通用changeset中区分插入/更新场景

在自定义的changeset函数里,用Ecto.Changeset.persisted?/1判断当前记录是否已持久化(即是否是更新场景),针对该场景添加专属逻辑。

示例代码(密码哈希场景):

defmodule Api.User do
  use Ecto.Schema
  import Ecto.Changeset

  schema "users" do
    field :email, :string
    field :username, :string
    field :password_hash, :string
    # 虚拟字段,用于接收明文密码
    field :password, :string, virtual: true
    field :password_confirmation, :string, virtual: true
  end

  def changeset(user, attrs) do
    user
    |> cast(attrs, [:email, :username, :password, :password_confirmation])
    |> validate_required([:email, :username])
    |> validate_confirmation(:password)
    # 仅在更新场景触发的逻辑:如果是已持久化记录且密码字段有值,执行哈希
    |> maybe_hash_password()
  end

  defp maybe_hash_password(changeset) do
    if persisted?(changeset) and get_change(changeset, :password) do
      changeset
      |> put_change(:password_hash, Bcrypt.hash_pwd_salt(get_change(changeset, :password)))
    else
      changeset
    end
  end
end

更新时直接调用这个自定义changeset函数,而不是Ecto.Changeset.change/2:

iex> user = Repo.get_by(User, email: "email")
iex> changeset = User.changeset(user, %{password: "password", password_confirmation: "password"})
iex> Repo.update(changeset)

2. 编写专属的更新用changeset函数

如果更新场景的逻辑和插入差异较大,可以单独定义一个update_changeset/2函数,仅处理更新时的需求:

defmodule Api.User do
  # ... 其他代码省略 ...

  def update_changeset(user, attrs) do
    user
    |> cast(attrs, [:password, :password_confirmation])
    |> validate_confirmation(:password)
    |> hash_password_if_present()
  end

  defp hash_password_if_present(changeset) do
    case get_change(changeset, :password) do
      nil -> changeset
      password -> put_change(changeset, :password_hash, Bcrypt.hash_pwd_salt(password))
    end
  end
end

更新时调用这个专属函数:

iex> user = Repo.get_by(User, email: "email")
iex> changeset = User.update_changeset(user, %{password: "password", password_confirmation: "password"})
iex> Repo.update(changeset)

关键注意点

  • 永远不要直接用Ecto.Changeset.change/2来处理需要自定义逻辑的更新操作,它不会触发任何你定义的校验或转换逻辑。
  • 用persisted?/1判断更新场景是最可靠的方式,它会检查记录是否已经被持久化到数据库中。

内容的提问来源于stack exchange,提问作者Basil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 04:32:08