Function in FallbackController generated by Phoenix generator is never called. What is it for?

I used mix phx.gen.json to generate a simple User resource. Among the files it generated, two of them are

user_controller.ex

defmodule MyapiWeb.UserController do
  use MyapiWeb, :controller

  alias Myapi.Accounts
  alias Myapi.Accounts.User

  action_fallback MyapiWeb.FallbackController

  def index(conn, _params) do
    users = Accounts.list_users()
    render(conn, "index.json", users: users)
  end

  def create(conn, %{"user" => user_params}) do
    with {:ok, %User{} = user} <- Accounts.create_user(user_params) do
      conn
      |> put_status(:created)
      |> put_resp_header("location", Routes.user_path(conn, :show, user))
      |> render("show.json", user: user)
    end
  end

  def show(conn, %{"id" => id}) do
    user = Accounts.get_user!(id)
    render(conn, "show.json", user: user)
  end

  def update(conn, %{"id" => id, "user" => user_params}) do
    user = Accounts.get_user!(id)

    with {:ok, %User{} = user} <- Accounts.update_user(user, user_params) do
      render(conn, "show.json", user: user)
    end
  end

  def delete(conn, %{"id" => id}) do
    user = Accounts.get_user!(id)

    with {:ok, %User{}} <- Accounts.delete_user(user) do
      send_resp(conn, :no_content, "")
    end
  end
end

and fallback_controller.ex

defmodule MyapiWeb.FallbackController do
  @moduledoc """
  Translates controller action results into valid `Plug.Conn` responses.

  See `Phoenix.Controller.action_fallback/1` for more details.
  """
  use MyapiWeb, :controller

  def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
    IO.inspect(changeset)
    conn
    |> put_status(:unprocessable_entity)
    |> put_view(MyapiWeb.ChangesetView)
    |> render("error.json", changeset: changeset)
  end

  def call(conn, {:error, :not_found}) do
    conn
    |> put_status(:not_found)
    |> put_view(MyapiWeb.ErrorView)
    |> render(:"404")
  end
end

My question is regarding the def call(conn, {:error, :not_found}) function in fallback_controller.ex. When it is called ?

  • When a User is created or updated which is not valid, def call(conn, {:error, %Ecto.Changeset{} = changeset}) is called. I know how fallback with with works.

  • When show or delete function in user_controller.ex is called with the wrong user_id, Ecto.NoResultsError is converted to 404 response.

So, what is the point of def call(conn, {:error, :not_found}) function in fallback_controller.ex ? When is it called ?

1 Like

call in fallback module will be called when you return non-Plug.Conn value from your controller function. In this case it can happen in 2 places:

  • if the Accounts.create_user/1 fails to return {:ok, %User{}}
  • if the Accounts.update_user/2 fails to return {:ok, %User{}}
1 Like

Yes I know how fallback controlller works but Accounts.create_user/1 and Accounts.update_user/2 will either return {:ok, %User{}} or {:error, %Ecto.Changeset{}}. When would they return {:error, :not_found} ?

It would be called if you write Context.get_thing in another controller action when using with. It’s a common pattern so Phoenix supports it.

2 Likes

When you say it, do you mean call(conn, {:error, %Ecto.Changeset{} = changeset}) or call(conn, {:error, :not_found}) ?

Both. Phoenix includes a set of common fallback patterns out of the box, and you may choose to add more.

2 Likes

Please read my post again. I know how fall back works. Both will not be called for the same req. Either call(conn, {:error, %Ecto.Changeset{} = changeset}) will be called or call(conn, {:error, :not_found}). I Know when call(conn, {:error, %Ecto.Changeset{} = changeset}) gets called. I dont know when call(conn, {:error, :not_found}) gets called.

You don’t need to yell…

1 Like

It is called when you return {:error, :not_fount} from the handler. I have no idea when you do that, but when you will, then it will be called. There is no magic there. You are author of the code above, not us, so we cannot tell you when you return what value from the functions and my crystal ball broke last week, so I cannot look into your code, sorry.

1 Like

Apologies, not my intention.

You are correct. It is not used by default. Please open up an issue: we should either remove it or add a code comment to it.

1 Like

Yes, I can see that call(conn, {:error, :not_found}) will be called when {:error, :not_found} is somehow returned :slight_smile: But cant cant figure out when that would happen, cant see any scenario when anyone would return {:error, :not_found}

Thank you :slight_smile: The first time creator of elixir himself has replied to my post :slight_smile:

Weird, I mostly do exactly that because all of my previous Phoenix applications had no database and therefore I simply return {:error, :not_found} when something isn’t found (e.g. state stored in a file or when trying to call a GenServer related to a specific subject which could possibly not exist yet).

1 Like

For me it’s useful because I don’t like controllers raising intentionally. Exceptions imo should not be used for control flow and not finding an item in the db for a supplied id is nothing exceptional nor unexpected.

3 Likes

Not weird. I was talking about the default files generated by the mix phx.gen.json generator not returning {:error, :not_found}. You can choose to edit and return whatever you want.

But I see no problem when the default files cover a very common pattern. Just because someone prefers to not use them, doesn’t mean they should not be generated.

1 Like

I also see no problem. My post was not about removing them in default scenario, it was about finding out who was calling it in the default scenario. Wether to keep it or remove it is above my pay grade.

You’ve edited this post now, but I was still sent an email with the original message and it was very rude and unpleasent to recieve.

Please try to remember that the people you are talking to on the internet are people, you should treat them with courtesy, especially if they are taking time out of their day to help you with your problem.

9 Likes