Check_constraint/3 doesn't catch my constraint violation

Hello,

I’ve setup a check constraint in my migration which is working correctly, but the changeset doesn’t pick up the error using Check_constraint/3. Can somebody please help?

Here’s what I have in my migration:

create constraint(:users, “users_password_not_null_check”, check: “parent_role_id is not null or (parent_role_id is null and password is not null)”)

and here the model:

def changeset(%User{} = user, attrs) do
user
|> cast(attrs, [:name])
|> validate_required([:name])
|> unique_constraint(:user_name, message: “Username is already taken.”)
|> check_constraint(:password, name: “users_password_not_null_check”, message: “Password is required.”)
end

The constraint violation is beeing raised by postgres, but just not catched by the changeset.

Can you show the full error?

Sure, here it is:

[debug] QUERY ERROR db=6.1ms
INSERT INTO “users” (“name”,“password”,“user_name”,“inserted_at”,“updated_at”) VALUES ($1,$2,$3,$4,$5) RETURNING “id” [“Gian Marc”, “abc”, “gmc”, {{2018, 2, 25}, {16, 46, 28, 785586}}, {{2018, 2, 25}, {16, 46, 28, 786996}}]
[debug] QUERY OK db=0.1ms
rollback []
** (Ecto.ConstraintError) constraint error when attempting to insert struct:

* unique: users_user_name_index

If you would like to convert this constraint into an error, please
call unique_constraint/3 in your changeset and define the proper
constraint name. The changeset has not defined any constraint.

Your error is on the user_name field, not the password field. The specific index that is being violated is unique: users_user_name_index, not your check constraint.

So to catch it you need to change this line:

|> unique_constraint(:user_name, message: “Username is already taken.”)

To:

|> unique_constraint(:user_name, name: "users_user_name_index", message: “Username is already taken.”)

This is necessary because the various constraints can not always auto-guess the name of the constraint that they are meant to be checking.

Ah thanks, I’ve just noticed, that actually no constraint got catched by the changeset, not even the unique constraint.

I’ve investigated a bit further and found that testing using the following statements is actually working:

changeset = User.changeset(%User{}, %{name: “BlaBla”, user_name: “bla”})
{:error, changeset} = Repo.insert(changeset)

It’s just not working using the seed script:

mix run priv/repo/seeds.exs

Using that one the warning appears that there is no unique_constraint/3 defined… So it seems that the changeset is not implicitly used while creating records using those statements:

Repo.insert!(%User{user_name: “mlc”, password: “”, name: “Marie”})

That’s indeed the case. Ecto.Repo does simply use what you pass it. If it’s a changeset than it’s using the changeset. If it’s plain data (and the used function allows for it) than it just uses that. While Repo.insert does allow for plain data Repo.update does require you to pass a changeset, because otherwise it wouldn’t be able to determine the actual changes in the data.

3 Likes