Phoenix框架Ecto中timestamp字段自动填充updated_at与inserted_at的方法
嘿,我来帮你搞定Ecto里自动维护inserted_at和updated_at的问题!你的两种尝试里,第一种其实是官方推荐的正确方向,但第二种写法存在明显问题,我来一步步拆解:
为什么你的第二种写法不生效?
直接给字段设置default: :utc_datetime是错误的——:utc_datetime只是一个表示类型的原子,Ecto不会把它自动转换成当前UTC时间,而且这种写法也没有任何逻辑来在更新时自动刷新updated_at字段,自然达不到预期效果。
正确的实现方式(优先用官方宏)
Ecto内置了timestamps/1宏,这是处理时间戳的最优解,它会自动帮你完成:
- 定义
inserted_at和updated_at字段 - 插入数据时自动填充当前UTC时间到两个字段
- 更新数据时自动刷新
updated_at为当前时间
你的第一个示例其实是对的,只需要确保配合标准的Ecto操作流程即可:
schema "users" do field :newsletter, :boolean, default: false # 这一行就足够了,type: :utc_datetime 指定用UTC时间 timestamps(type: :utc_datetime) end # 对应的changeset(如果需要做字段验证/转换) def changeset(user, attrs) do user |> cast(attrs, [:newsletter]) # 这里可以加其他验证逻辑,比如validate_required之类的 end
使用的时候不管是通过changeset插入:
Repo.insert(User.changeset(%User{}, %{newsletter: true}))
还是直接插入结构体:
Repo.insert(%User{newsletter: true})
inserted_at和updated_at都会自动被填充为当前UTC时间。当你调用Repo.update/2更新数据时,updated_at也会自动刷新。
自定义时间戳字段(如果需要)
如果你的表不用inserted_at/updated_at这种默认字段名,也可以通过timestamps/1的参数自定义:
schema "users" do field :newsletter, :boolean, default: false # 自定义字段名,同时指定时间类型 timestamps(inserted_at: :created_at, updated_at: :modified_at, type: :utc_datetime) end
手动实现(不推荐,除非特殊需求)
如果你非要手动定义字段并控制逻辑,可以用changeset钩子来处理:
schema "users" do field :newsletter, :boolean, default: false field :inserted_at, :utc_datetime field :updated_at, :utc_datetime end def changeset(user, attrs) do user |> cast(attrs, [:newsletter]) |> validate_required([]) |> maybe_set_inserted_at() |> set_updated_at() end # 只有插入且inserted_at为空时才设置 defp maybe_set_inserted_at(changeset) do if is_nil(get_field(changeset, :inserted_at)) do put_change(changeset, :inserted_at, DateTime.utc_now()) else changeset end end # 每次操作都更新updated_at defp set_updated_at(changeset) do put_change(changeset, :updated_at, DateTime.utc_now()) end
不过这种方式不如timestamps宏简洁,而且无法处理直接通过SQL插入数据的场景(timestamps宏会设置数据库层面的默认值),所以除非有特殊需求,优先用官方宏。
内容的提问来源于stack exchange,提问作者Gotrek
相关产品推荐
相关产品推荐

