How to handle unique constraint when using put_assoc

Hi community,

I am doing a test case and I have an associative table with FKs user_id and role_id. When I tried to insert with the repeated FKs (1, 1) like:

|> User.creation_changeset(%{
  email: "",
  password: "superstrongpassword",
  role_ids: [1, 1] # I have some pre-process to convert ID list to object
|> Repo.insert()

My user_role changeset:

def changeset(user_role, attrs) do
  |> cast(attrs, [:user_id, :role_id])
  |> validate_required([])
  |> unique_constraint([:role_id, :user_id], name: :user_role_items_user_id_role_id_index)

And this is the error i get:

     ** (Ecto.ConstraintError) constraint error when attempting to insert struct:
         * user_role_items_user_id_role_id_index (unique_constraint)
     If you would like to stop this constraint violation from raising an
     exception and instead add it as an error to your changeset, please
     call `unique_constraint/3` on your changeset with the constraint
     `:name` as an option.
     The changeset defined the following constraints:
         * users_email_index (unique_constraint)

# ...

I have tested the changeset, it is working fine. Just that the changeset cannot be hit in put_assoc/3.

Thank you so much in advance.

Best wishes,
Jing Hui PANG.

are you sure that you made a unique index before you query?

quote “”"
The changeset defined the following constraints:
* users_email_index (unique_constraint)

I am guessing that you just created email index only.

Yes that is the boilerplate code from user auth:

def creation_changeset(user, attrs, opts \\ []) do 
  |> cast(attrs, [:email, :password])
  |> validate_email()
  |> validate_password(opts)
  |> confirm_changeset()
  |> put_assoc(:roles, parse_roles(attrs)) 

defp validate_email(changeset) do
  |> validate_required([:email])
  |> validate_format(:email, ~r/^[^\s]+@[^\s]+$/, message: "must have the @ sign and no spaces")
  |> validate_length(:email, max: 160)
  |> unsafe_validate_unique(:email, Perfreview.Repo)
  |> unique_constraint(:email)