I am currently going through the Programming Phoenix 1.4 book and so far am really enjoying it. I didn’t think I’d appreciate a lack of ‘magic’ as much as I do.
Anyhow, I’m a bit stumped on how one thing works and would appreciate someone pointing me in the direction of where in the Elixir doc’s I could see what’s going on.
In a controller, I (was instructed to) wrote(ite) the following code:
def action(conn, _) do
args = [conn, conn.params, conn.assigns.current_user]
apply(__MODULE__, action_name(conn), args)
end
and then I was able to write:
def new(conn, _params, current_user) do
changeset = Multimedia.change_video(current_user, %Video{})
render(conn, "new.html", changeset: changeset)
end
This code pulls the current_user id from the conn and let’s me access it as an argument in any other function within that controller module. What I don’t understand, is how this modification to other functions is working, and why/how it doesn’t force the other controller actions to have it as an argument.
What I mean is, why can I still write: def index(conn, _params) do and have no problems, but elsewhere, I’m also able to write def new(conn, _params, current_user) do all because of that one function.
defmodule D3jsDemoWeb.PageController do
use D3jsDemoWeb, :controller
def action(conn, _) do
args = [conn, conn.params]
apply(__MODULE__, action_name(conn), args)
end
def index(conn, _params) do
render(conn, "index.html")
end
end
This fails:
defmodule D3jsDemoWeb.PageController do
use D3jsDemoWeb, :controller
def action(conn, _) do
args = [conn, conn.params. :ok]
apply(__MODULE__, action_name(conn), args)
end
def index(conn, _params) do
render(conn, "index.html")
end
end
with
UndefinedFunctionError at GET /
function D3jsDemoWeb.PageController.index/3 is undefined or private
on the index action when rendering the page - which is the behaviour I would have expected if you left def index(conn, _params) do in place for that controller after adding the third parameter in the action function.
after
defmodule D3jsDemoWeb.PageController do
use D3jsDemoWeb, :controller
def action(conn, _) do
args = [conn, conn.params, :ok]
apply(__MODULE__, action_name(conn), args)
end
def index(conn, _params, _other) do
render(conn, "index.html")
end
end
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
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
@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.