Validating changeset params with the existing fields

My question is around Ecto.Changeset and the need to validate entire models, not just the passed in params. Basically, I’ve found a useful pattern is to write a set of validations; for a specific action; against the model after the params are filter; and after the params are applied. The first two items were awkward to do in rails 4 while the later easy. So if a model is loaded from the database, the passed in params in combination to the fields already set on the model (or perhaps in the session) are what are validated. Validating just the passed in params is also useful (eg. enforcing that the password was sent along with the sensitive account update request) but done less often.

Ecto.Changest is exciting because it decouples the changes from the model, however, most of the validations run only against the params, not the fields in the data after the params are applied. What I want is the option to validate_change, etc using not get_change nor really even get_field but just the data field after the params are filtered and cast. Obviously if the changes make the model invalid then the model won’t be saved (and one could image a copy of the un-modified struct along with the modified version anyways). Technically using get_field could work as well.

Rewriting new functions that apply the changes and then validate the model seem to not be in spirit with Ecto.Changeset and I wish there is a solution that could work with existing models and libraries that leverage it.
I am looking for thoughts around how these types of changes are meant to be handled in elixir (especially during a typical request to update a resource in Phoenix) and how that pattern can fit into this. Radical departures to what I am looking for are great to hear but having a specific answer is also appreciated.

get_change(changeset, :field) is basically the same as changeset |> apply_changes() |> Map.get(:field), so I’m not really sure what you’re missing for it. Also Ecto.Changeset does validate just the changes, as the idea is that a given source struct is already valid and only the params need to be validated, but you can just as well validate any given change against it’s consequences for the validity of the whole struct.

3 Likes

Can you give a more concrete example of what you want to achieve but currently is not possible?

I’m not sure if I understand what you want to say.

But it sounds as if you want to validate if a combination of fields is still valid even when only one has been changed?

Lets say the sum of :a and :b always have to be 10?

Shoouldn’t that be easily possible by a custom validator?

Something like:

if get_field(cs, :a) + get_field(cs, :b) != 10 do
  cs
  |> add_error(:a, "Must sum to 10", [other: :b])
  |> add_error(:b, "Must sum to 10", [other: :a])
else
  cs
end
2 Likes

I think that is usually referred to as cross-field validation (in contrast to field level validation).

Shouldn’t that be easily possible by a custom validator?

+1


Ecto.Changeset.get_field/3:

While get_change/3 only looks at the current changes to retrieve a value, this function looks at the changes and then falls back on the data, finally returning default if no value is available.

Thanks for the help! I’ve had to move ahead today with progress so I’m basically taking @NobbZ advice. Soon I want to take the time to post concrete examples with embedded form schemas, ecto schemas, hybrids, etc. Once all of the examples are laid out it should be clear what the general pattern that I am looking for is that works well with each example.

1 Like

Or go the python way and add a Future module built-in to Elixir or so that when used like use Future, otp: 21 then it compiles with OTP 21 features or so, though this might require the Elixir source itself to be built for multiple OTP versions and to load them based on the declaration (thus the use Future, ... bit might have to be in the mix.exs file?).