ErrorView and undefined function translate_error/1

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?

The dreaded missing s :scream_cat:

1 Like

@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 :smile:

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