Clean way to get error messages from Ecto.changeset

If I want to get the first error message from Ecto.Changeset, here’s what I have to do:

error_msg = changeset.errors
        |> hd()
        |> Tuple.to_list()
        |> tl()
        |> hd()
        |> Tuple.to_list()
        |> hd()

As you can see, it’s very troublesome. Any idea how to fix it?

How about pattern matching it? That’s how I extract error messages in my tests.

Phoenix provides the following helper when you use AppName.DataCase in your tests

This doesn’t give you the “first” error, but you can use it to check errors in any order you’d like.

  @doc """
  A helper that transforms changeset errors into a map of messages.

      assert {:error, changeset} = Accounts.create_user(%{password: "short"})
      assert "password is too short" in errors_on(changeset).password
      assert %{password: ["password is too short"]} = errors_on(changeset)

  """
  def errors_on(changeset) do
    Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
      Regex.replace(~r"%{(\w+)}", message, fn _, key ->
        opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()
      end)
    end)
  end

Here’s how I do it, FWIW!

Code abbreviated for clarity.

  {:error, changeset} ->
        {error_field, {error_reason, _}} = Enum.at(changeset.errors, 0)

        {:noreply,
         socket
         |> put_flash(
           :error,
           "Error. #{String.capitalize(to_string(error_field))} #{error_reason}"
         )}
-        {error_field, {error_reason, _}} = Enum.at(changeset.errors, 0)
+        [{error_field, {error_reason, _}} | _] = changeset.errors         

This is a thing of beauty! I didn’t even think of doing it this way but it is absolutely the Elixir way…

There are loads of examples sprinkled across the internet. Basically variations of changeset error to string.

Pretty old thread but it has a decent example.

I think I’ve written this function in every serious elixir project I’ve done.