A repository of bitesize articles, tips & tricks
(in both English and French) curated by Mirego’s team.

Gestion des contraintes PostgreSQL personnalisées avec Ecto

Ecto est capable de bien gérer des contraintes d’une base de données PostgreSQL : unique_violation, foreign_key_violation, exclusion_violation et check_violation.

Par contre, il ne gère pas la violation de contraintes personnalisées qu’il est possible de déclarer dans des fonctions appellées en tant que trigger. Par exemple, une fonction pourrait lever cette exception :

RAISE EXCEPTION 'Custom exception 👋' USING ERRCODE = 'integrity_constraint_violation';

Pour la convertir en erreur Ecto, par exemple dans une insertion, on doit capturer l’exception Postgrex.Error levée par Ecto :

defmodule MyApp.Repo do
  use Ecto.Repo,
    adapter: Ecto.Adapters.Postgres,
    otp_app: :my_app

  defoverridable insert: 2

  def insert(changeset, opts) do
    super(changeset, opts)
  rescue
    exception in Postgrex.Error ->
      handle_postgrex_exception(exception, __STACKTRACE__, changeset)
  end

  defp handle_postgrex_exception(%{postgres: %{code: :integrity_constraint_violation, message: message}}, _, changeset) do
    changeset = Ecto.Changeset.add_error(changeset, :base, message)
    {:error, %{changeset | valid?: false}}
  end

  defp handle_postgrex_exception(exception, stacktrace, _) do
    reraise(exception, stacktrace)
  end
end

Lorsqu’Ecto tente d’insérer un changeset dans la base de données et qu’une erreur Postgrex.Error est levée, il tentera maintenant de la gérer lui-même, selon le code de violation lancée par notre fonction.

Si le code est géré, on peut ajouter une erreur personnalisée au changeset; sinon, l’erreur est relancée.

{:error, changeset} =
  %MyApp.Record{}
  |> Ecto.Changeset.change()
  |> MyApp.Repo.insert()

changeset.errors # => [base: "Custom exception 👋"]