Ecto changeset shows errors One by one

Hello, my Changeset only shows one error per time, I don’t need to show errors One by one, how can I show the all errors needed?

now it shows like this:

[
  lastname: {"MSG.",
   [count: 3, validation: :length, kind: :min]}
]

but after fixing this it will show me another error, but I need like this:

[
  name: {"MSG.",
   [count: 3, validation: :length, kind: :min]},
  lastname: {"MSG.",
   [count: 3, validation: :length, kind: :min]}
]

Ecto.Changeset.traverse_errors might be what you are looking for.

I used this:

  def convert_changeset_errors(changeset) do
    out =  Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} ->
          Enum.reduce(opts, msg, fn {key, value}, acc ->
            String.replace(acc, "%{#{key}}", to_string(value))
          end)
      end)
    out
  end

Changeset it self just shows one error per time, it has one error every time. when you fix first error it will show you second error. why doesn’t it show all error in first time?

  def add_user(attrs) do
    add = %UserSchema{}
    |> UserSchema.changeset(attrs)
    |> Repo.insert()
    case add do
      {:ok, user_info}    -> {:ok, :user_add, user_info}
      {:error, changeset} -> {:error, :user_add, changeset}
    end
  end

I mean {:error, changeset} has one error, Actually it has 3 errors.
Thanks

Can you share the code where you define the validations please?

1 Like

yes sure, Thanks

def changeset(struct, params \\ %{}) do
  struct
  |> cast(params, [:name, :lastname, :username, :email, :password_hash, :password, :sex, :role, :type])
  |> validate_required([:name, :lastname, :username, :email, :sex, :role, :type], message: "فیلد مذکور نمی تواند خالی باشد.")
  |> validate_inclusion(:sex, ["Man", "Female", "Other"])
  |> validate_inclusion(:role, ["user", "admin"])
  |> validate_inclusion(:type, ["normal", "facebook"])
  |> validate_length(:name, min: 3, max: 20, message: "نام شما باید بین ۳ الی ۲۰ کاراکتر باشد.")
  |> validate_length(:lastname, min: 3, max: 20, message: "نام خانوادگی شما باید بین ۳ الی ۲۰ کارکتر باشد.")
  |> validate_length(:password, min: 7, max: 200, message: "پسورد شما باید حداقل ۷ کاراکتر باشد و حداکثر ۲۰۰ لطفا پسورد مناسبی انتخاب کنید.")
  |> validate_length(:username, min: 3, max: 20, message: "نام کاربری شما باید بین ۳ الی ۲۰ کارکتر باشد.")
  |> validate_length(:email, min: 8, max: 50, message: "تعداد کارکتر های ایمیل باید بین ۸ تا ۵۰ عدد باشد.")
  |> unique_constraint(:username, name: :index_of_users_username, message: "نام کاربری وارد شده از قبل وجود دارد.")
  |> unique_constraint(:email, name: :index_of_users_email, message: "ایمیل وارد شده از قبل وجود دارد.")
  |> hash_password
end

  defp hash_password(changeset) do
    case changeset do
      %Ecto.Changeset{valid?: true, changes: %{password: password}} ->
        put_change(changeset, :password_hash, Bcrypt.hash_pwd_salt(password))
      _ -> changeset
    end
  end

is there somebody here to help me to fix this? :frowning_face:

I found my problem where it is created!!! this error includes my

create(
      index(:homygram_user, [:email],
        concurrently: true,
        name: :index_of_users_email,
        unique: true
      )
    )
    create(
      index(:homygram_user, [:verified_email],
        concurrently: true,
        name: :index_of_users_verified_email,
        unique: true
      )
    )
    create(
      index(:homygram_user, [:username],
        concurrently: true,
        name: :index_of_username,
        unique: true
      )
    )

The other fields have no problem when is invalid!!

what is the exact problem?

that said :verified_email is not being cast, and you are missing the unique_constraint check for the unique index on :verified_email

So I assume :verified_email will be NULL for the first user written to the DB - and subsequent inserts will fail since they also have verified_email NULL - which violates the unique constraint on verified_email

why/what is the logic behind that verified_email unique index? (and consider removing it…)

Hello, pleas skip the :verified_email, I have 2 fields on my db that are indexed and uniq, now in the one request my user sends me the parameters I need, and if these two indexed field have a problem like the email and username exist before, Changeset shows one error the first error is email and the user sends request agin it shows username error. it can’t show all error of the fields indexed.

I found the post I think they had my problem:

it is bad that my user sed 2 or 3 request to see all errors, user should see the all errors in first request

what is use case here? API? what view “renders” the error?

usually you just pass the changeset with errors to the view…

I’ve not checked postgres, but if it’s like other databases I’ve used this behaviour is because it checks the constraints one at a time, so ecto will only ever received one error.

To work around this, you need to hit the database first, verify uniqueness yourself, and use the constraints as a fallback to prevent race conditions.

This should do what you need: https://hexdocs.pm/ecto/Ecto.Changeset.html#unsafe_validate_unique/4

1 Like

Hello, I saw this , but I didn’t understand how to use?? would you mind explaining this? or writing a sample code?

unsafe_validate_unique(changeset, fields, repo, opts \\ [])

what is repo ? I should use it changeset pipeline ? and it says we should use unique_constraint(changeset, field, opts \\ [])

Therefore, a unique_constraint/3 should also be used to ensure your data won’t get corrupted.

I’m confused

if you just print changeset directly you will get one error, please test this problem, I think you don’t know what my problem is!!? :frowning_face:

Yep, they go in your changeset pipeline. repo is your ecto repo, defined in your app.

|> unsafe_validate_unique([:email], Repo)
|> unsafe_validate_unique([:username], Repo)

This will make ecto go to the database and verify the fields are unique before attempting the insert.

The reason you still need unique_constraint/3 is for the case where records have been inserted between ecto checking the database and performing the insert. These are already in your pipeline, and you have the indexes so you’re all set. Should just be those two lines above which you need.

2 Likes

Please try to consolidate those calls into a single one, like in |> unsafe_validate_unique([:email, :username], Repo). Ecto can then optimise the query better and might fit it into a single query, while 2 calls means at least 2 queries.

1 Like

Hello, when I use these fields in a list I will not be able to write 2 message like this:

    |> unsafe_validate_unique([:username], AuthService.Repo, message: "username must be unique within state")
    |> unsafe_validate_unique([:email], AuthService.Repo, message: "email must be unique within state")

I can’t write 2 custom message, can I?

If you pass multiple fields, the validation will put the error against the first one, which is similar behaviour to just using the unique_constraint validator. I believe OP wants two messages, one for each unique index on the table and I could only get it to work with 2 calls. It is possible with a single query, just not with the current unsafe_validate_unique/4 implementation.

2 Likes

Yeah, I realiise this now as well.

But to be honest, I’d drop this unsafe_validate_unique completely and fully trust the constraints.

The unsafe in the name is there for a reason. It may pass those validations and still fail the actual creation because of race conditions, and then we are back to one message per constraint anyway.

doesn’t unique_constraint protect our project?

    |> unsafe_validate_unique([:username, :email], AuthService.Repo, message: "ایمیل و نام کاربری نمی تواند از قبل وجود داشته باشد.")
    |> unique_constraint(:username, name: :index_on_users_username, message: "نام کاربری وارد شده از قبل وجود دارد.")
    |> unique_constraint(:email, name: :index_on_users_email, message: "ایمیل وارد شده از قبل وجود دارد.")

I’d say the race condition is an edge case here. The reason for having the first check is for UX, not technical reasons, i.e. A user can be told if both their username and email are taken with a single form submission.

1 Like

If either is taken, I’d try a password recovery first anyway…

Apart from that, isn’t about everyone already checking during filling out the form if the username and email are taken via some JavaScript as I type?

But yeah, at the end everyone has to decide on his own, what they’ll do, but if one uses a function that already has unsafe in its name, then one should understand the implications that the usage of this function has.