Redirecting with anon fn within the router

I want to redirect one route in the router to another one. Something like:

scope "/sub" do
  resources "/a", AController
  resources "/b", BController
  # redirect "/" to "/sub/a"
end

It seems straightforward to do:

get "/", fn conn, _opts -> Phoenix.Controller.redirect(to: "/sub/a") |> halt end, nil

but I keep getting an error that “protocol String.Chars not implemented” for my anon function. I tried adding a final parameter to get of [as: :dummy] to handle the naming of the derived helper, but it doesn’t change the error message.

Why does that 2nd parameter have to implement String.Chars? Is there some other way to let me use an anonymous function as a plug?

I have a solution to use an action in the PageController to do the redirect and map the route to that action, but it feels too indirect. I’d rather keep it all in the router for future maintainability. I know I could write a separate Plug module to do this, but again, it’s more indirection than I would prefer. Is there some other approach to doing a simple redirect like this within the router?

You can do this with a plug:

scope "/sub" do
  resources "/a", AController
  resources "/b", BController
  get "/", Redirect, to: "/sub/a"
end

defmodule Redirect do
  def init(opts), do: opts

  def call(conn, opts) do
    conn
    |> Phoenix.Controller.redirect(opts)
    |> halt()
  end
end
5 Likes

To expand, the Phoenix router has never allowed or supported anonymous functions being passed for the plug in the 2nd argument to get and friends.

1 Like

Thanks @chrismccord ! I guess that answers my question as authoritatively as I could have hoped. I’ll probably need to dig into the source to satisfy my curiosity as to why an anon function can’t be used (I also tried a named function to no avail). I guess that means the router macros only accept module plugs. Understanding that helps my mental model a great deal.

1 Like

Btw we need to include use MyApp, :controller in the Redirect module, otherwise it breaks on the halt() fn.

defmodule Redirect do
  use MyApp, :controller

  def init(opts), do: opts

  def call(conn, opts) do
    conn
    |> Phoenix.Controller.redirect(opts)
    |> halt()
  end
end

@rafbgarcia Or use the Plug call straight. :slight_smile:

As Overmind said, you don’t need to use MyAppWeb, :controller, you can call the halt() function from Plug.Conn directly (or import it), |> Plug.Conn.halt()

1 Like

Thanks, I didn’t know that :slight_smile: