Should changeset remove errors after recasting with valid data?

I’m getting back into developing with Elixir and was doing some experiments with Ecto, I noticed that creating an invalid ecto changeset and then updating it by recasting with new data, the old errors that now shouldn’t be present are still in the errors list. Is that correct? Am I updating the changeset wrong?

Sample:

product.ex
defmodule Product do
  use Ecto.Schema
  import Ecto.Changeset

  embedded_schema do
    field(:name, :string)
    field(:size, :float, default: 0.0)
  end

  def new(params) do
    %__MODULE__{}
    |> update(params)
  end

  def update(product, params) do
    product
    |> cast(params, [:name, :size])
    |> validate_required([:name])
    |> validate_number(:size, greater_than: 0)
    |> validate_length(:name, min: 5, max: 50)
  end
end

IEx Session:

iex(11)> p = Product.new(%{name: "Ae", size: 30})
#Ecto.Changeset<
  action: nil,
  changes: %{name: "Ae", size: 30.0},
  errors: [
    name: {"should be at least %{count} character(s)",
     [count: 5, validation: :length, kind: :min, type: :string]}
  ],
  data: #Product<>,
  valid?: false
>
iex(12)> Product.update p, %{name: "Apples"}      
#Ecto.Changeset<
  action: nil,
  changes: %{name: "Apples", size: 30.0},
  errors: [
    name: {"should be at least %{count} character(s)",
     [count: 5, validation: :length, kind: :min, type: :string]}
  ],
  data: #Product<>,
  valid?: false
>

Versions:
Ecto: 3.8.4

lock checksum
"ecto": {:hex, :ecto, "3.8.4", "e06b8b87e62b27fea17fd2ff6041572ddd10339fd16cdf58446e402c6c90a74b", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f9244288b8d42db40515463a008cf3f4e0e564bb9c249fe87bf28a6d79fe82d4"},

It does not remove errors for values that were previously bad but have come good. This caught me out too at first. Use the original data and current params to build a new one.

3 Likes

Plus you may think you could %{changeset | errors: [], valid?: true} to recast it

BUT it will not clear the validations list so you could have « a memory leak ».

You better to use changeset.data and re cast your changes

Validations are excluded from the IO.inspect but it exists :wink:

I don‘t understand, where can I read more about this ?

This is the desired behavior.

You can remove the errors manually with changeset = %{changeset | errors: []} or Map.put/3 since changesets are maps essentially.

As reasoning, i have found this answer from Jose.