Phoenix框架Fallback Action报错求助(使用with宏时触发)
with Macro in Phoenix Controller Hey there, fellow Elixir/Phoenix newbie! Let’s figure out why your with macro is triggering the fallback action and get it sorted out.
First, Let’s Break Down the Root Cause
When you use with without an explicit else block, any failed pattern match will return the unmatched value directly instead of handling it. Phoenix controllers expect you to return a Plug.Conn.t() (the modified connection with a response) or a structured error tuple like {:error, reason} that your fallback action knows how to process.
If your Monitor.is_valid_hash/1 returns a boolean (e.g., false when invalid), a failed match like true <- false will make the with macro return false—which isn’t a valid conn or error tuple. Phoenix sees this as an unhandled case and triggers the fallback action.
Example of the Problem Code
Let’s assume your code looks something like this (super common for new Phoenix devs):
Router (router.ex)
get "/ping/:hash", ListenerController, :ping
Controller (listener_controller.ex) - Without with (works fine)
def ping(conn, %{"hash" => hash}) do if Monitor.is_valid_hash(hash) do conn |> json(%{status: "ok"}) else conn |> put_status(:bad_request) |> json(%{error: "invalid hash"}) end end
Controller - With with (triggers fallback)
def ping(conn, %{"hash" => hash}) do with true <- Monitor.is_valid_hash(hash) do conn |> json(%{status: "ok"}) end end
Two Simple Fixes
1. Add an else Block to Handle Failed Matches
Explicitly handle the unmatched case in with to return a valid conn response:
def ping(conn, %{"hash" => hash}) do with true <- Monitor.is_valid_hash(hash) do conn |> json(%{status: "ok"}) else false -> conn |> put_status(:bad_request) |> json(%{error: "invalid hash"}) end end
2. Use Tagged Tuples for Clearer Error Handling (Recommended)
Modify your Monitor.is_valid_hash/1 function to return tagged tuples instead of booleans. This makes the with logic more readable and avoids accidental fallback triggers:
Updated monitor.ex
def is_valid_hash(hash) do # Replace this with your actual validation logic if String.length(hash) == 32 do {:ok, hash} else {:error, :invalid_hash_length} end end
Updated Controller ping/2
def ping(conn, %{"hash" => hash}) do with {:ok, _valid_hash} <- Monitor.is_valid_hash(hash) do conn |> json(%{status: "ok"}) else {:error, :invalid_hash_length} -> conn |> put_status(:bad_request) |> json(%{error: "Hash must be 32 characters long"}) end end
This way, every path through your controller returns either a valid conn response or a structured error that you can handle explicitly—no more unexpected fallback triggers!
Hope that clears things up. Let me know if you need to tweak this for your specific validation logic!
内容的提问来源于stack exchange,提问作者Gayan Hewa

