nelsonic

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:

But still no closer to understanding the why/when/how we would use __using__/1 macro … :frowning:

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

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.

Where Next?

Popular in Questions Top

Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
Kurisu
For example for a current url like http://localhost:4000/cosmetic/products?_utf8=✓&query=perfume&page=2, I would like to get: ...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New

Other popular topics Top

lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
axelson
This post is a wiki (feel free to hit the edit button near the bottom right of this post to add your own changes!) This post collects co...
239 48342 226
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New

Latest on Elixir Forum

We're in Beta

About us Mission Statement