How to re-evaluate a changeset in Ecto?

Hi all,
I have a changeset that looks like this:

  action: nil,
  changes: %{avatar: "joe2.jpg"},
  errors: [
    avatar: {"can't be blank", [validation: :required]},
    avatar_caption: {"can't be blank", [validation: :required]}
  data: #Storybook.Examples.Schemas.Person<>,
  valid?: false

I manually added the avatar: change with put_change.
How can I re-evaluate the changeset so that the avatar: related error is removed?

Thank you

Create a new changeset instead of modifying an existing one.

I’ve described this in a bit longer from in a blog post of mine:

The changeset API is a very functional one. Given a base and an input of how things are meant to look like at the end ecto figures out all the necessary changes to get to that endresult. One can then validate all the changes to prevent disallowed invariant and eventually can check if the changes are good to go or not.

The API however is not stateful in the sense that after such a round of validation one could go back and add more changes and have the changeset know which errors won’t apply anymore. A changeset doesn’t keep track of which validations were applied end how, it only keeps their results – errors and metadata – around.

So whenever there is new input with changes to be validated the expectation is that a new changeset is created, which again is run through the same validation paths as the previous.

1 Like

Great article! Kudos!

Unfortunately I have not the full set of params at my disposal.
I am in a phx-change callback of a particular form input.

Can I get the params from the old changeset, and create a new one?

I can probably avoid using the phx-change on the specific input, and use a callback when the form change, so that I have all the params

A quick way out would be using changeset.params.

Hi! I was facing the same problem. Following @LostKobrakai’s great blog post, I had the following in the handle_event responsible for removing lines:

|> Ecto.Changeset.put_embed(:lines, lines)
|> to_form()

In order the revalidate my changeset upon line removal, I wrote this instead:

|> Ecto.Changeset.put_embed(:lines, lines)
|> Ecto.Changeset.apply_changes()
|> Item.change_item() # returns changeset
|> struct!(action: :validate)
|> to_form()

The problem with changeset.params is that it stores the parameters as given on changeset creation, thus not reflecting the changes we made with put_embed.

Is there a better way?

If the goal is to handle errors you could split creation of the changeset from validation, so you can do the edits on the changeset in between those two calls.