nelsonic
Why/How/When to use the __using__(which) macro in Phoenix Controller/View/etc?
This is a bit of a “strange” question but please bare with me; I expect it will be useful/insightful to others learning Elixir/Phoenix wanting to understand the __using__/1 macro.
Context
Inside every Phoenix (Elixir Web Framework) App, at the bottom of the /lib/{yourapp}_web.ex file
e.g: /lib/chat_web.ex
there is a __using__/1 macro defined as:
@doc """
When used, dispatch to the appropriate controller/view/etc.
"""
defmacro __using__(which) when is_atom(which) do
apply(__MODULE__, which, [])
end
Question: what is the __using__/1 macro used for?
If you can, share (or link to) a usage example which will help demonstrate it in a real world context.
Where does the apply function come from given that it’s not “imported” in the /lib/{yourapp}_web.ex file and what is the effect of the “apply” ?
We have tried googling and reading several docs, tutorials, blog posts etc. on Macros. e.g:
- https://elixir-lang.org/getting-started/meta/macros.html
- Metaprogramming · Elixir School
- https://hackernoon.com/understanding-elixir-macros-3464e141434c
- https://medium.com/@Mike_Andr/understanding-elixirs-macros-by-phoenix-example-e99827a60987
But still no closer to understanding the why/when/how we would use __using__/1 macro …
If we attempt to comment out or delete it from lib/chat_web.ex the app does not compile even though it is not invoked from with chat_web.ex … and excoveralls (test coverage reporting) reports that it’s not being executed.
I find this confusing / non-beginner-friendly and searching the Phoenix guide (docs) is not particularly insightful e.g:
https://github.com/phoenixframework/phoenix/blob/29536f3b86154ab64647643a3eeeb263e33834cd/guides/controllers.md
Further context:
In the Phoenix Chat Example/Tutorial: GitHub - dwyl/phoenix-chat-example: 💬 The Step-by-Step Beginners Tutorial for Building, Testing & Deploying a Chat app in Phoenix 1.7 [Latest] 🚀 · GitHub
We are tracking test coverage as a learning exercise …
There is only one line of code that is not being covered by Tests:
Codecov
How is it that the line is not being executed (“covered”) when we run the tests, but if we comment out the line the tests fail?
Is this macro “magic” in that it is being “used” without actually being called?
Any shoshin insight much appreciated!
Most Liked
peerreynders
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.
Eiji
@nelsonic: Here are some helpful resources:
Popular in Questions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance











