How to forcefully redirect a user to a certain page properly?

I want to redirect a user to a certain page if “some_condition” is met. For instance, a user must create an article if there’re no articles created at all. The redirect must happen from all the controllers and actions in them.

I have a plug:

defmodule P1 do
  alias Plug.Conn

  def init(opts), do: opts

  def call(conn, opts) do
    if some_condition do

      Phoenix.Controller.redirect(conn, to: MyApp.Router.Helpers.article_path(conn, :new))
      |> Plug.Conn.halt()
    end
  end
end

An issue with this is that

a) it’ll redirect all the requests to “article_path/new”, including ones to js, css, etc … files. Namely, it’ll occur multiple times in a row per one route. Instead of one single redirect, as I want.

b) a page won’t be rendered. It’ll be displayed as an empty page when I redirect a user this way

How to fix this?

It seems your plug says what to do when some_condition is met but returns anything in the else case. Usually It should just return conn in the “else” block.

   def call(conn, opts) do
    if some_condition do
      conn
      |> redirect(to: MyApp.Router.Helpers.article_path(conn, :new))
      |> Plug.Conn.halt()
    else
      conn
    end
  end

You solution won’t work because when some_condition is met, the 2 issues described above will remain.

some_condition may be “get_some_data_from_database()”

Well I just wanted to highlight that a plug in Elixir must always return conn, and a function must always return an explicit value. If these two points are not verified, nothing will work at all!

For the rest, just call the plug in the right place and it should be fine. I will suggest to define a pipeline in the router and make all the requests (except the one that will allow to create the missing resource) pass through it, and that should be fine. ^^

I’m calling it in the right place and it’s not fine.

Ok where do you call it exactly? Can you show some a sample of your code?

Maybe the some_condition function also, but I assume it returns a boolean value, true if redirection is needed and false if not needed.

In endpoint.ex.

  plug(MyApp.P1)
  plug(MyAppWeb.Router)

Well you will plug it in the endpoint only when you want it to be called for all requests without exception.

Since you want to force the user to add an article, you have to allow him to access at least the page to perform that requirement at MyApp.Router.Helpers.article_path(conn, :new).

So in the router file you have a default browser pipeline right? Just define another one and name it for example :restricted like below:

  pipeline :restricted do
    plug :browser

    # Ensure that user has added at least one article.
    plug MyApp.P1
  end

Make sure all your routes pass through the restricted pipeline except the one that will allow to add an article which should pass just through the normal :browser pipeline.

Are you trying to guess the answer? Have you tried it yourself?
That won’t work – all the requests to assets still go via :browser which will cause it to get triggered multiple times if I reload a page.

I don’t need “pipeline restristed”. Do you understand my question?

I just saw your reply in my mails.

The assets are served by Plug.Static which is called in the top of your endpoint. As long as you keep the default setup all your assets should be served at the root of your endpoint url.

I don’t tried your exact Plug but I have a lot of similar plugs in my Phoenix projects that work.
For example plugs that require user to be authenticated, or to add a profile picture if authenticated etc.
None of them prevent my public assets to be served.

To put it simple you’re facing infinite redirection loop because your Plug is called for any request even the one that should allow the user to add an article. To avoid that you have to call your plug in the router as I suggested.