controllerelixirplug

Phoenix - Callback action between controller and view


I am building an admin tool app for our system. I want to record every action made by every user.

Here's what I did

defmodule AdminToolWeb.UserController do
  use AdminToolWeb, :controller
  
  ...

  def delete(conn, %{"id" => id}) do
    current_user = Guardian.Plug.current_resource(conn)

    with %User{} <- user = Accounts.get_user(id) do
      Accounts.delete_user(user)

      conn
      |> put_flash(:info, "#{user.id} deleted.")
      |> Activities.log(current_user)
      |> redirect(to: Routes.user_path(conn, :index))
    end
  end

  ...
end

The problem is I have to pipe |> Activity.log(current_user) in every action of every controller I have in my app.

Is there a way to implement something like this? Controller -> (ActivityLogPlugOfSorts) -> View using a custom plug and call it like this?

defmodule AdminToolWeb.UserController do
  use AdminToolWeb, :controller
  import AdminToolWeb.Plugs.Activities

  plug :log

...

but It should be called between controller and view.

Or should I put a function inside a View module instead?

I hope there is a better way.


Solution

  • What you're looking for here are controller plugs which can indeed be inserted directly at the controller level. They will run before the controller action, so you will not have the opportunity when the plug runs to know if the attempted action will be successful. However you can use the controller plug to setup a callback that will be run after the controller action (but before the response is sent). An example might be:

    defmodule HelloWeb.Plugs.ActionLogger do
      import Plug.Conn
      require Logger
    
      def init(default), do: default
    
      def call(conn, _default) do
        register_before_send(conn, fn conn ->
          if (response_code_2xx?(conn) do
            Logger.info("action taken: #{extract_action(conn)}")
          end
          conn
        end)
      end
    end
    

    where response_code_2xx?/1 and extract_action/1 are left as exercises for the reader.