How to render constraint error as JSON in create action in controller?

I tired the following but getting error:

(Poison.EncodeError) unable to encode value: {"is a duplicate", []}

Code:

  def create(conn, %{"engagement" => engagement_params}) do
    case Engagements.create_engagement(engagement_params) do
      {:ok, engagement} ->
        conn
        |> put_flash(:info, "Engagement created successfully.")
        |> redirect(to: engagement_path(conn, :show, engagement))
      {:error, %Ecto.Changeset{} = changeset} ->
        #render(conn, "new.html", changeset: changeset)
        pretty_json(conn, changeset)
    end
  end

  def pretty_json(conn, data) do
    conn
    |> Plug.Conn.put_resp_header("content-type", "application/json; charset=utf-8")
    |> Plug.Conn.send_resp(200, Poison.encode!(data, pretty: true))
  end

The error is because in the changeset there’s a tuple somewhere (probably the error field?) and Poison doesn’t know how to encode that. You can write a function to extract the relevant bits.

FYI perhaps there’s already something to deal with this, like the json views?

Poison cant encode lists. I am not sure maybe tuples too. I switched to elixir json for that.

1 Like

wouldn’t you do something like:

%{errors: MyApp.Web.ChangesetView.translate_errors(changeset)}

eg:

{:error, %Ecto.Changeset{} = changeset} ->
        #render(conn, "new.html", changeset: changeset)
        pretty_json(conn, %{errors: MyApp.Web.ChangesetView.translate_errors(changeset)})

additionally shouldn’t your api return 422 or similar on errors? (not 200)

1 Like

I have managed to render the errors as JSON like this:

a. added the following to error_view.ex:

  def translate_errors(changeset) do
    Ecto.Changeset.traverse_errors(changeset, &translate_error/1)
  end

  def render("error.json", %{changeset: changeset}) do
    # When encoded, the changeset returns its errors
    # as a JSON object. So we just pass it forward.
    %{errors: translate_errors(changeset)}
  end

My create action in controller looks like this now:

  def create(conn, %{"engagement" => engagement_params}) do
    case Engagements.create_engagement(engagement_params) do
      {:ok, engagement} ->
        conn
        |> put_flash(:info, "Engagement created successfully.")
        |> redirect(to: engagement_path(conn, :show, engagement))
      {:error, %Ecto.Changeset{} = changeset} ->
        #render(conn, "new.html", changeset: changeset)
        conn
        |> put_status(:unprocessable_entity)
        |> render(HubWeb.ErrorView, "error.json", changeset: changeset)
    end
  end

Next I will be looking for a way to provide localized translations for those error messages.

8 Likes

Thank you so much for this!

2 Likes

Thank you :slight_smile:

1 Like