Changeset validation unique, issue while inserting

Hi guys! I am having trouble inserting data into the database. Here is my schema with changeset:

defmodule CompanyVisualisation.CompanyVisualisation do
  use Ecto.Schema
  import Ecto.Changeset

  schema "users" do
    field :email, :string
    field :name, :string
    field :salary, :integer
    field :surname, :string
  end

  @doc false
  def changeset(company_visualisation, attrs) do
    company_visualisation
    |> cast(attrs, [:name, :surname, :email, :salary])
    |> validate_required([:name, :surname, :email, :salary])
    |> unique_constraint(:email, name: "users_user_email", message: "Email is already taken.")
  end
end

and here is the code I am trying to run;

changeSet = CompanyVisualisation.CompanyVisualisation.changeset(%CompanyVisualisation.CompanyVisualisation{}, %{"name" => name, "surname" => surname, "email" => email, "salary" => salary})
    if changeSet.valid? do
      IO.inspect(changeSet)
      case CompanyVisualisation.Repo.insert(%CompanyVisualisation.CompanyVisualisation{:name => name, :surname => surname, :email => email, :salary => String.to_integer(salary)}) do
        {:ok, _struct} ->

Error is in the case statement, that constraint error when attempting to insert struct and

users_email_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 has not defined any constraint.

The thing I want to achieve is to validate an email and check, whether it is not already taken (that’s why it is unique). Where do I have mistakes?

So far, i have changed my changest set, and it looks like this now:

  def changeset(company_visualisation, attrs) do
    company_visualisation
    |> cast(attrs, [:name, :surname, :email, :salary])
    |> validate_required([:name, :surname, :email, :salary])
    |> unique_constraint(:email, name: :users_email_index, message: "Email is already taken.")
  end

And source code looks like that:

changeSet = CompanyVisualisation.CompanyVisualisation.changeset(%CompanyVisualisation.CompanyVisualisation{}, %{"name" => name, "surname" => surname, "email" => email, "salary" => salary})
    if changeSet.valid? do
      IO.inspect(changeSet)
      case CompanyVisualisation.Repo.insert(%CompanyVisualisation.CompanyVisualisation{:name => name, :surname => surname, :email => email, :salary => String.to_integer(salary)}) do
        {:ok, _struct} ->
          conn
          |> put_flash(:info, "Added user.")
          |> redirect(to: Routes.main_path(conn, :index), users: CompanyVisualisation.Repo.all(CompanyVisualisation.CompanyVisualisation))
        {:error, struct} ->
          IO.puts("addUserToDatabase")
          IO.inspect(struct)
          conn
          |> put_flash(:error, "Could not add user.")
          |> redirect(to: Routes.main_path(conn, :defineUser))
      end
    else
      conn
        |> put_flash(:error, "Could not add user.")
        |> redirect(to: Routes.main_path(conn, :defineUser))
    end

However, instead of redirecting to :defineUser and putting an error, it returns an error in the browser.

Hi @MatOsinski

have you tried to omit the “name” option in unique_constraint/3? At least for Postgresql and unique index Ecto figures it out own it’s own.

1 Like

Yeah I think @Clandestino is right. Just to be sure, can you show the migration where you set up the unique index?

1 Like

@benwilson512
Hi guys! Yes, I have tried to omit the :name option, however I do still get the same error :((
This is my migration;

defmodule CompanyVisualisation.Repo.Migrations.CreateUsers do
  use Ecto.Migration

  def change do
    create table(:users) do
      add :name, :string
      add :surname, :string
      add :email, :string
      add :salary, :integer
    end

    create unique_index(:users, [:email])
  end
end
CompanyVisualisation.Repo.insert(%CompanyVisualisation.CompanyVisualisation{:name => name, :surname => surname, :email => email, :salary => String.to_integer(salary)})

This is your issue. You should be inserting your changeset, not building a new struct and inserting that. Overall, the function should look more like:

changeSet = CompanyVisualisation.CompanyVisualisation.changeset(%CompanyVisualisation.CompanyVisualisation{}, %{"name" => name, "surname" => surname, "email" => email, "salary" => salary})
IO.inspect(changeSet)
case CompanyVisualisation.Repo.insert(changeset) do
  {:ok, _struct} ->
    conn
    |> put_flash(:info, "Added user.")
    |> redirect(to: Routes.main_path(conn, :index), users: CompanyVisualisation.Repo.all(CompanyVisualisation.CompanyVisualisation))
  {:error, struct} ->
    IO.puts("addUserToDatabase")
    IO.inspect(struct)
    conn
    |> put_flash(:error, "Could not add user.")
    |> redirect(to: Routes.main_path(conn, :defineUser))
end

You don’t need the if changeset.valid? check either. If it isn’t valid, Repo.insert(changeset) will return {:error, changeset}

2 Likes

Oh, yes! You are completely right! That’s a mistake I have not seen before. Thank you so much!!!

1 Like