Creating Unique Constraint/Index Ecto.Migration: Elixir
Have you ever found yourself in a position where you can find anything about something you really need? Just happen to encounter this problem doing my task for a company I’m working with and I can’t find proper documentation about it. So here it is, hope I'm the last one who had troubles trying and finding the right way to do this.
Migration
Let's start with the migration, say you want to create a movie table with two fields (title and genre) and you don't want to create entries with duplicate values of those fields.
create table(:movies) do
add(:genre, :string)
add(:title, :string) timestamps()
endcreate(
unique_index(
:movies,
~w(title genre)a,
name: :index_for_movies_duplicate_entries
)
)
index_for_movies_duplicate_entries: can be any name you want, can also be automatically generated based on the fields included on your constraint.
~w(title genre)a: a list of fields to checks for a unique constraint. Can also be [:title, :genre], I just like the sigil one.
Schema
Make sure to pipe the unique_constraint/3 on your changeset.
defmodule Data.Schemas.Movie do
use Data.Schema schema "movies" do
field(:genre, :string)
field(:title, :string) timestamps()
end@required ~w(title)a
@optional ~w(genre)a def changeset(struct, params \\ %{}) do
struct
|> cast(params, @optional ++ @required)
|> validate_required(@required)
|> unique_constraint(
:title,
name: :index_for_movies_duplicate_entries
)
end
end
The 2nd parameter of unique_constraint/3 can be any field that’s a part of the constraint, in this case, the title.
Test
To test this unique constraint, you just need to insert two entries with the same values.
defmodule Data.Schemas.MovieTest do
use Data.SchemaCase
alias Data.Schemas.Movie
test "testing changeset with unique contraints" do
params = %{
title: "Taken 4",
genre: "action"
}
%Movie{}
|> Movie.changeset(params)
|> Repo.insert()
m2 = %Movie{}
|> Movie.changeset(params)
{:error, changeset} = Repo.insert(m2)
refute changeset.valid?
end
end
The changeset should not be valid, hence the refute changeset.valid? and return this error
got: #Ecto.Changeset<action: :insert,
changes: %{title: "Taken 4", genre: "action"}, 6157
errors: [value: {"has already been taken", []}],
data: #Data.Schemas.Movie<>,
valid?: false>
And there you have it. hope this can help. 🎉
If you find this helpful, I’d appreciate a coffee 😊