Let me walk through the %Ecto.changeset{}
and validations/1
data that is being passed around in my app.
To start, this here is what the registration form could look like. In this example only the password field has all requirements listed. Those requirements will change, by the way, but just to give a better idea of what UI I am referring to.

At page load I get this data:
#Ecto.Changeset<
action: nil,
changes: %{},
errors: [
password: {"can't be blank", [validation: :required]},
email: {"can't be blank", [validation: :required]},
username: {"can't be blank", [validation: :required]}
],
data: #TodayForum.Accounts.User<>,
valid?: false
>
# validations/1
[
password: {:format, ~r/[!?@#$%^&*_0-9]/},
password: {:format, ~r/[A-Z]/},
password: {:format, ~r/[a-z]/},
password: {:length, [min: 12, max: 72]},
email: {:unsafe_unique, [fields: [:email]]},
email: {:length, [max: 160]},
email: {:format, ~r/^[^\s]+@[^\s]+$/}
]
The errors and validations here are indeed correct, but there are two things missing.
- The errors of the other validation requirements that are not met.
For example, at page load password requires at least one capital letter
requirement is also not met, but that error is not present in the changeset. I understand that this is per design, but in my use case I require all errors to show.
- The
:message
data that has been bound to the changeset.
So for example here:
defp validate_password(changeset, opts) do
changeset
|> validate_required([:password])
|> validate_length(:password, min: 12, max: 72)
|> validate_format(:password, ~r/[a-z]/, message: "at least one lower case character") # HERE
|> validate_format(:password, ~r/[A-Z]/, message: "at least one upper case character") # HERE
|> validate_format(:password, ~r/[!?@#$%^&*_0-9]/, message: "at least one digit or punctuation character") # HERE
|> maybe_hash_password(opts)
end
If I enter an invalid password, like:

I get the following data:
#Ecto.Changeset<
action: nil,
changes: %{password: "**redacted**"},
errors: [
password: {"at least one digit or punctuation character",
[validation: :format]},
password: {"at least one upper case character", [validation: :format]},
password: {"should be at least %{count} character(s)",
[count: 12, validation: :length, kind: :min, type: :string]},
email: {"can't be blank", [validation: :required]},
username: {"can't be blank", [validation: :required]}
],
data: #TodayForum.Accounts.User<>,
valid?: false
>
# validations/1
[
password: {:format, ~r/[!?@#$%^&*_0-9]/},
password: {:format, ~r/[A-Z]/},
password: {:format, ~r/[a-z]/},
password: {:length, [min: 12, max: 72]},
email: {:unsafe_unique, [fields: [:email]]},
email: {:length, [max: 160]},
email: {:format, ~r/^[^\s]+@[^\s]+$/}
]
So for the password I get the right errors.
So here is the problem. I want to dynamically create the list of requirements underneath the password field. And dynamically update the styling of these list items depending on whether these requirements are passed or not. With the current changeset and validations info I cannot get it done. Because I need all the errors and all the validations (including the messages) to be sent to the client at page load and any change to the form.
I could only show the list of requirements after the first form change by the user. This eliminates the need for all errors to be sent to the client on page load. That’s fine. But then I still need to validation messages to be passed to the client.