Function Adding Args to Controller Actions

I was simply stating that I couldn’t find any evidence to support this observation:

why/how it doesn’t force the other controller actions to have it as an argument.

My observation was that customizing the arguments by overriding action/2 forces an update of the parameter list of every action function within that controller because they all have the same arity, argument order and types.


Any controller includes something like this:

  use RumblWeb, :controller

which injects code

  # from rumbl/lib/rumbl_web.ex

  def controller do
    quote do
      use Phoenix.Controller, namespace: RumblWeb

      import Plug.Conn
      import RumblWeb.Gettext
      alias RumblWeb.Router.Helpers, as: Routes
    end
  end

then

  use Phoenix.Controller, namespace: RumblWeb

injects even more code phoenix/lib/phoenix/controller.ex at 19b0b01e2fc751901e06fcd74db1fc5b39672d26 · phoenixframework/phoenix · GitHub

  defmacro __using__(opts) do
    quote bind_quoted: [opts: opts] do
      import Phoenix.Controller

      # TODO v2: No longer automatically import dependencies
      import Plug.Conn

      use Phoenix.Controller.Pipeline, opts

      plug :put_new_layout, {Phoenix.Controller.__layout__(__MODULE__, opts), :app}
      plug :put_new_view, Phoenix.Controller.__view__(__MODULE__)
    end
  end

then

  use Phoenix.Controller.Pipeline, opts

injects among other things this code phoenix/lib/phoenix/controller/pipeline.ex at 19b0b01e2fc751901e06fcd74db1fc5b39672d26 · phoenixframework/phoenix · GitHub

      @doc false
      def init(opts), do: opts

      @doc false
      def call(conn, action) when is_atom(action) do
        conn = update_in conn.private,
                 &(&1 |> Map.put(:phoenix_controller, __MODULE__)
                      |> Map.put(:phoenix_action, action))

        phoenix_controller_pipeline(conn, action)
      end

      @doc false
      def action(%Plug.Conn{private: %{phoenix_action: action}} = conn, _options) do
        apply(__MODULE__, action, [conn, conn.params])
      end

      defoverridable [init: 1, call: 2, action: 2]

init/1 and call/2 define a module plug that adds the conn.private.phoenix_controller and conn.private.phoenix_controller values to the Plug.Conn struct.

action/2 is the controller function normally used to extract conn.private.phoenix_action and conn.params to call the appropriate controller action function with

  apply(__MODULE__, action, [conn, conn.params])

But because action/2 is listed as defoverridable you are free to provide your own implementation of action/2 inside your controller module to invoke the action functions whichever way you wish.

Now you may be wondering

  def call(conn, action) when is_atom(action) do

where is action coming from?

As far as I can tell it originates from router.ex (Phoenix.Router.scope/2):

  scope path: "/api/v1", as: :api_v1, alias: API.V1 do
    get "/pages/:id", PageController, :show
  end

Phoenix.Router.get/4:

  get(path, plug, plug_opts, options \\ [])

If GET /api/v1/pages/:id then

  • API.V1.PageControllerplug
  • :showplug_opts

See also:

5 Likes