Why/How/When to use the __using__(which) macro in Phoenix Controller/View/etc?

I have a demo app “rlp” from this topic. Looking at rlp_web.ex I notice this:

This can be used in your application as:

      use RlpWeb, :controller
      use RlpWeb, :view

Shortly thereafter this:

  def controller do
    quote do
      use Phoenix.Controller, namespace: RlpWeb
      import Plug.Conn
      import RlpWeb.Router.Helpers
      import RlpWeb.Gettext
    end
  end

  def view do
    quote do
      use Phoenix.View, root: "lib/rlp_web/templates",
                        namespace: RlpWeb

      # Import convenience functions from controllers
      import Phoenix.Controller, only: [get_flash: 2, view_module: 1]

      # Use all HTML functionality (forms, tags, etc)
      use Phoenix.HTML

      import RlpWeb.Router.Helpers
      import RlpWeb.ErrorHelpers
      import RlpWeb.Gettext
    end
  end

  def router do
    quote do
      use Phoenix.Router
      import Plug.Conn
      import Phoenix.Controller
    end
  end

  def channel do
    quote do
      use Phoenix.Channel
      import RlpWeb.Gettext
    end
  end

One thing you should notice is that each of these functions wrap:

quote do
...
end

That should tell you that the controller, view, router, channel functions generate code. This happens at compile-time, not run-time. So these functions are intended for compile-time use, not run-time use.

  defmacro __using__(which) when is_atom(which) do
    apply(__MODULE__, which, [])
  end

apply is simply Kernel.apply/3. And again defmacro tells you that you are dealing with a compile-time construct.

So

use RlpWeb, :controller

would become at compile time

Kernel.apply(RlpWeb, :controller, [])

which is equivalent to

RlpWeb.controller()

which refers to that function we’ve just come across. So in page_controller.ex:

defmodule RlpWeb.PageController do
  use RlpWeb, :controller

  def index(conn, _params) do
    render conn, "index.html"
  end
end

The use RlpWeb, :controller causes RlpWeb.controller() to be run at compile time which results in the code wrapped in quote do ... end being dumped unceremoniously into RlpWeb.PageController resulting in

defmodule RlpWeb.PageController do
  use Phoenix.Controller, namespace: RlpWeb
  import Plug.Conn
  import RlpWeb.Router.Helpers
  import RlpWeb.Gettext

  def index(conn, _params) do
    render conn, "index.html"
  end
end

Notice how there is now another use there that needs exactly the same treatment during compile time.

Understanding exactly what code becomes available when during compilation can be a bit confusing. For that How does Elixir compile/execute code? may help.

9 Likes