Updating Changeset Errors

I am trying to update a changeset from an invalid set of changes to a valid set, but the errors do not reflect the change. The following is what I am experiencing, but not what I expected.

  def changeset(foo \\ %__MODULE__{}, attrs) do
    foo
    |> cast(attrs, [:top_value, :bottom_value])
    |> validate_required([:top_value, :bottom_value])
    |> validate_number(:top_value, greater_than: 0)
    |> validate_number(:bottom_value, greater_than: 0)
  end

  iex> cs = Foo.changeset(%{top_value: 10})
  #Ecto.Changeset<
    action: nil,
    changes: %{top_value: 10},
    errors: [bottom_value: {"can't be blank", [validation: :required]}],
    data: #MyApp.Foo<>,
    valid?: false
  >

  iex(6)> Foo.changeset(cs, %{bottom_value: 10})
  #Ecto.Changeset<
    action: nil,
    changes: %{bottom_value: 10, top_value: 10},
    errors: [bottom_value: {"can't be blank", [validation: :required]}],
    data: #MyApp.Foo<>,
    valid?: false
  >

I would like to have the errors recalculated after updating the changeset. Is this possible? Am I using this changeset in an unsupported manner? I have also tried both Ecto.Changeset.put_change/3 and Ecto.Changeset.update_change/3. Any suggestions are appreciated.

You‘d need to remove those errors on your own. Validations are purely additive.

2 Likes

Instead of removing errors, simply pass the original struct back into the changeset function but with more parameters added.

1 Like

That’s what we thought. But is it by design or something that you just should not do or just something that never came up? ^^

It has come up, but it’s not really possible without complicating how changesets work by a lot.

changeset
|> validate_number(:field, less_than: 25)
|> validate_number(:field, greater_than: 10, less_than: 50)

How should this be handled, if there were the possibility to update errors? Especially imagining both validations actually being abstracted in multiple different purpose functions. It would take a great amount of validation specific logic to accurately update those errors, which doesn’t at all translate well to custom validations a user creates. Also users would need to know those update rules as well, to prevent any strange surprises.

3 Likes

Hello!

This is an old topic by I comes here after a few searches.

I solved this problem using the changeset with an empty struct and updating the attrs with put_change. Following @Cmeurer example will be something like this:

def changeset(attrs) do
    # Always use an empty struct
    %__MODULE__{}
    |> cast(attrs, [:top_value, :bottom_value])
    |> validate_required([:top_value, :bottom_value])
    |> validate_number(:top_value, greater_than: 0)
    |> validate_number(:bottom_value, greater_than: 0)
end

cs = Foo.changeset(%{top_value: 10})
# Here has an error

# In my case this put_change is done in another function
cs = Ecto.Changeset.put_change(cs, :bottom_value, 10)
# Here added bottom_value, changeset still has an error

cs = Foo.changeset(cs.changes)
# Here errors are gone and valid? == true

1 Like