How do you implement unique_constraint/3 for a clustered index?
If I have a unique index on the below table, how do I call unique_constraint in my changeset? I ask because the function typespec requires an atom as the field term, but a clustered index is a list.
unique_constraint(Ecto.Changeset.t, atom, Keyword.t)
create table(:letters)
add :a, :string
add :b, :string
add :c, :string
end
create unique_index(:letters, [:a, :b], name: :letters_unique_index)
def changeset(struct, params \\ %{})
struct
|> cast(params, [:a, :b, :c])
|> unique_constraint(???, name: :letters_unique_index)
end
Thanks!
1 Like
AFAIK, the first argument for unique_contraint/3
will be used just for the errors map creation. You can use one of :a
or :b
if you want the error to show on your form, or even make up your own like :unique
and read it in the changeset.errors[:unique]
.
PS.: @josevalim maybe we could explain this situation clearly in the Ecto.Changeset.unique_contstraint/3
documentation, it caused the same confusion to me sometime ago.
1 Like
I tried this, but the shell still renders an Ecto.ConstraintError
error when I run tests to check the constraint. I tried using a random atom, :uniq
to identify the clustered unique index, and also the atom of the first field in the index. Unfortunately, neither produces a changeset error.
Could you please send a pull request or, if you can’t for some reason, open
up an issue? Thank you!
1 Like
Well, I tried here and everything worked as I mentioned:
# migration
create table(:team_memberships) do
add :team_id, references(:teams, on_delete: :delete_all)
add :user_id, references(:users, on_delete: :delete_all)
timestamps()
end
create unique_index(:team_memberships, [:team_id, :user_id],
name: :team_memberships_relation_index)
# model
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:team_id, :user_id])
|> unique_constraint(:user_id, name: :team_memberships_relation_index)
end
With the above code, the following twice in the shell:
Hippo.insert(TeamMembership.changeset(%TeamMembership{}, %{team_id: 1, user_id: 1}))
Returned me a tuple:
{:error,
Ecto.Changeset<action: :insert, changes: %{team_id: 1, user_id: 1},
errors: [user_id: {"has already been taken", []}],
data: TeamMembership<>, valid?: false>}
Oh good! I must be doing something wrong. I’ll give it another shot.
I got it working, thanks!
1 Like