I’ve got errors_view.ex in Phoenix, trying to render some JSON error when the changeset doesn’t pass while creating a record on a table.
defmodule BloodbathWeb.ErrorView do
use BloodbathWeb, :view
def translate_errors(changeset) do
Ecto.Changeset.traverse_errors(changeset, &translate_error/1)
end
def render("error.json", %{changeset: changeset}) do
%{errors: translate_errors(changeset)}
end
def render("400.json", _assigns) do
%{errors: ["Bad request"]}
end
def render("401.json", _assigns) do
%{errors: ["Unauthorized"]}
end
def render("404.json", _assigns) do
%{errors: ["Page not found"]}
end
def render("500.json", _assigns) do
%{errors: ["Internal server error"]}
end
# In case no render clause matches or no
# template is found, let's render it as 500
def template_not_found(_template, assigns) do
render "500.json", assigns
end
end
I’ve seen this traverse_error multiple times searching on Internet, including in this forum, but when trying to boot it it throws this
== Compilation error in file lib/bloodbath_web/rest/views/error_view.ex ==
** (CompileError) lib/bloodbath_web/rest/views/error_view.ex:9: undefined function translate_error/1
(elixir 1.11.4) src/elixir_locals.erl:114: anonymous fn/3 in :elixir_locals.ensure_no_undefined_local/3
(stdlib 3.14) erl_eval.erl:680: :erl_eval.do_apply/6
(elixir 1.11.4) lib/kernel/parallel_compiler.ex:314: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/7
What am I doing wrong here?
@al2o3cr Thanks for your answer, but I think that’s not it, I’ve seen this example in lots of places including this forum and it was always with a difference between translate_errors and translate_error I’m not really sure why or/and where it comes from … For example here
Anyway my work around was to do this
def render_with({message, values}) do
Enum.reduce values, message, fn {k, v}, acc ->
String.replace(acc, "%{#{k}}", to_string(v))
end
end
def render_with(message) do
message
end
def render("error.json", %{changeset: changeset}) do
errors = Enum.map(changeset.errors, fn {field, detail} ->
%{
"#{field}": render_with(detail)
}
end)
%{errors: errors}
end
Which’s basically just traversing the changeset errors by myself, I’m not 100% satisfied but it works so far in my case.
Thanks for taking the time anyway 
In my (fairly default) Phoenix app, the translate_error/1 function is defined in MyAppWeb.ErrorHelpers (located in the views directory). That module is imported into every view, see the view function in MyAppWeb.
For the sake of completeness, this is my (default generated) ErrorHelpers module:
defmodule MyAppWeb.ErrorHelpers do
@moduledoc """
Conveniences for translating and building error messages.
"""
use Phoenix.HTML
@doc """
Generates tag for inlined form input errors.
"""
def error_tag(form, field) do
Enum.map(Keyword.get_values(form.errors, field), fn error ->
content_tag(:span, translate_error(error),
class: "invalid-feedback",
phx_feedback_for: input_id(form, field)
)
end)
end
@doc """
Translates an error message using gettext.
"""
def translate_error({msg, opts}) do
# When using gettext, we typically pass the strings we want
# to translate as a static argument:
#
# # Translate "is invalid" in the "errors" domain
# dgettext("errors", "is invalid")
#
# # Translate the number of files with plural rules
# dngettext("errors", "1 file", "%{count} files", count)
#
# Because the error messages we show in our forms and APIs
# are defined inside Ecto, we need to translate them dynamically.
# This requires us to call the Gettext module passing our gettext
# backend as first argument.
#
# Note we use the "errors" domain, which means translations
# should be written to the errors.po file. The :count option is
# set by Ecto and indicates we should also apply plural rules.
if count = opts[:count] do
Gettext.dngettext(MyAppWeb.Gettext, "errors", msg, msg, count, opts)
else
Gettext.dgettext(MyAppWeb.Gettext, "errors", msg, opts)
end
end
end