Applying only valid changes in a changeset

I’m using a changeset to validate query params for search filters. I would only like to apply changes in params only when they’re valid. Is there a natural way to ignore or drop invalid changes in a changeset so that invalid query parameter values casted in my filter changeset are simply ignored or dropped?

It currently looks rougly like this:

def search_query(%Ecto.Changeset{} = filters, params) do
    filters
    |> cast_and_validate(params)
    |> Ecto.Changeset.apply_action!(:update) # except partially apply valid changes here
    # ...
end

I don’t think there’s any function in Ecto.Changeset doing what you want directly, but that should be pretty simple. You could do, once you’re changeset went through the cast and validations (untested):

changes = Map.drop(changeset.changes, Keyword.keys(changeset.errors))
Changeset.changes(filters, changes) # set the valid changes without going through validations again

If you’d like to keep into the original pipe, and using Elixir 1.12 and newer, using the wonderful then/2 (also untested):

def search_query(%Ecto.Changeset{} = filters, params) do
    filters
    |> cast_and_validate(params)
    |> then(&Ecto.Changeset.changes(filters, Map.drop(&1.changes, Keyword.keys(&1.errors))))
    |> Ecto.Changeset.apply_action!(:update)
    # ...
end

Note i assume that filters does not already contains changes, since Changeset.changes does not replace but rather merge into the existing.

3 Likes

That’s a very nice way to write it! Thank you for showing me Ecto.Changeset.change and then.