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