Is it possible to catch a foreign key exception with insert_all?

I’m using Ecto.Repo.insert_all/3 and I’d like to gracefully handle foreign key errors.

Explanatory code:

def insert_post(body, user_id) do
  Repo.insert_all(Post, [%{body: body, user_id: user_id}])
end

If the user_id does not exist in the users table then a Postgrex.Error is raised but I’d like to convert this to an error. With Ecto.Repo.insert/2 this is handled by adding a foreign key constraint, but since insert_all is lower-level this needs to be handled manually, which I understand. But I’d like to be able to handle it easier than this:

def insert_post(body, user_id) do
  Repo.insert_all(Post, [%{body: body, user_id: user_id}])
rescue
  e in Postgrex.Error ->
    if e.postgres.constraint == "posts_user_id_fkey" do
      {:error, "user does not exist"}
    else
      raise e
    end
end

Is there an approach that I’m missing?

From the reply quoted below on a related topic - which you should totally have a look on, you may use assoc_constraint/3 or foreign_constraint/3. For a more generic approach, instead of manually calling any of these functions or any other validation you might want to add like cast/4, I would recommend you do something like:

defmodule Post do
  use Ecto.Schema
  import Ecto.Changeset

  schema "post" do
    # ...
  end

  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:user_id])
    |> foreign_key_constraint(:user_id)
  end
end

###

params = %{body: body, user_id: user_id}

%Post{}
|> Post.changeset(params)
|> Repo.insert # {:ok, Ecto.Schema.t} | {:error, Ecto.Changeset.t}
1 Like