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?
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.
Thank you. This helped me derive the code I needed using a FallbackController approach:
defmodule HubWeb.FallbackController do
use HubWeb, :controller
def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
conn
|> put_status(:unprocessable_entity)
|> put_view(HubWeb.ChangesetView)
|> render("error.json", changeset: changeset)
end
end
def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
conn
|> put_status(:unprocessable_entity)
|> put_view(json: MyAppWeb.ChangesetJSON)
|> render("error.json", changeset: changeset)
end
The ChangesetJSON file automatically generated for me when using the generators, but just in case here’s what it looks like:
defmodule MyApp.ChangesetJSON do
@doc """
Renders changeset errors.
"""
def error(%{changeset: changeset}) do
# When encoded, the changeset returns its errors
# as a JSON object. So we just pass it forward.
%{errors: Ecto.Changeset.traverse_errors(changeset, &translate_error/1)}
end
defp translate_error({msg, opts}) do
# You can make use of gettext to translate error messages by
# uncommenting and adjusting the following code:
# if count = opts[:count] do
# Gettext.dngettext(MyAppWeb.Gettext, "errors", msg, msg, count, opts)
# else
# Gettext.dgettext(MyAppWeb.Gettext, "errors", msg, opts)
# end
Enum.reduce(opts, msg, fn {key, value}, acc ->
String.replace(acc, "%{#{key}}", fn _ -> to_string(value) end)
end)
end
end
This allows me to return changeset’s nicely from my APIs:
def register(conn, params) do
with {:ok, user} <- Accounts.register_user(params) do
conn
|> put_status(:ok)
|> put_view(MyAppWeb.AccountsJSON)
|> render(:register, user: user)
else
error ->
error
end
end