Why the paths in Phoenix require an atom instead of a function?

Why the paths in Phoenix require an atom instead of a function?

defmodule HelloWeb.Router do
  use HelloWeb, :router

  scope "/", HelloWeb do
    get("/", PageController, :index)
  end
end

why is not passed like a function get("/", PageController.index) .

1 Like

Runtime dynamic binding:

iex(4)> arg = "Hello"
"Hello"
iex(5)> IO.puts(arg)
Hello
:ok
iex(6)> Kernel.apply(IO, :puts, [arg])
Hello
:ok
iex(7)> 

Kernel.apply/3


2 Likes

Same reasoning as to why apply(module, fun, args) has the function name supplied as an atom: It’s the only non macro-magic way to pass a module and function to be called later without the overhead of a closure. Also keep in mind that phoenix is doing the namespaceing of controllers for you, so PageController isn’t even an existing module without the namespace prepended.

3 Likes

What that would do is call PageController.index/0 and return the result of that call into the get("/", result) call.

1 Like

I’m guessing that @edgarjrg meant something more like get("/", &PageController.index/2) which is a similar API to how Absinthe defines references to resolver functions.

2 Likes

Well in that case it’s because phoenix doesn’t call that function, rather it calls the ‘call’ function on the module, then the ‘action’ function on that module, which then sets up some other state and error handlers and so forth and ‘then’ calls that function on the same module, so it needs to know both the module and function on that module to call as it calls ‘other’ functions on that same module. :slight_smile:

1 Like

As @OvermindDL1 said, PageController.index is the same as PageController.index() so you probably meant &PageController.index/2. But the problem is that Phoenix do not call that function, at least not directly. What Phoenix does though is to call PageController.call(conn, :index). You can see this by yourself by creating plug like

defmodule MyPseudoController do
  @behaviour Plug

  @impl true
  def init(opts), do: opts

  @impl true
  def call(conn, args) do
    Plug.Conn.send_resp(conn, 200, inspect(args))
  end
end

And then defining route:

get "/path", MyPseudoController, :foo
2 Likes

Yes i actually meant get("/", &PageController.index/2), thank you all for the answers :pray:, it looked like unnecessary complexity but i see it’s the way for decoupling scope, and allow a lot of other steps to be performed, as well as to avoid recompiling, very interesting approach.

2 Likes