richjdsmith

richjdsmith

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.

Any help and explanation on this is appreciated!

Showing Posts 1 to 4

peerreynders

peerreynders

The source code actually updates all actions to 3 parameters.

For some more background

https://medium.com/@lostkobrakai/alternative-parameters-for-phoenix-controller-actions-edd2b8a4362a


This works:

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

everything works again.

Maartz

Maartz

So if I guess it right, it’s related to function clauses ? Right ?

peerreynders

peerreynders

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:

Maartz

Maartz

Wow, thanks ! That’s very helpful to understand what’s going on behind the scenes. No magic.

— All posts loaded —

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
jonnycharles
I’m in search of an Elixir library that offers PDF generation capabilities similar to Ruby’s Prawn. While there have been discussions abo...
New
spammy
I’m looking to build a personal workflow to quickly deploy web applications written in elixir/phoenix, for local consumption (ie not on t...
New
Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
dli
Before I dive in myself, did anyone successfully sprinkle Hologram into their existing LiveView app? Looking for hints regarding: Addi...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
subsaharancoder
I’ve followed the Phoenix LiveView file upload code here Uploads — Phoenix LiveView v1.0.0-rc.7 and so far everything works just fine wit...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
ausimian
Emily is an Elixir library that runs Nx computations on Apple’s MLX. Install it as the default Nx backend and Nx, defn, Axon, Nx.Serving,...
New

Latest on Elixir Forum

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews