Sanjibukai

Sanjibukai

Constraints in Routes

Hi everybody,

I wanted to know if we can have constraints in the route level. I mean defining a route with some constraints (based e.g. on the request sessions data).

Sorry if I do a parallel with rails, but in rails (as explained from here in the guides) we can have constraints on the routes.

The constraints I’m particularly interested in are those based on lambdas.
Let me give you an example use case I had in rails..
I have different kind of users (regular, admins, etc.) where I have its type stored in a session variable once logged in.
Then I can do the following:

constraints lambda { |req| req.session[:user_type] == 'Admin' } do
  get  '/dashboard', to: 'admin_dashboard#show', as: 'dashboard'
  ...
end

constraints lambda { |req| req.session[:user_type] == 'User' } do
  get  '/dashboard', to: 'user_dashboard#show', as: 'dashboard'
  ...
end

This example is a little extract and might not tell all the story but as you might noticed it’s somehow an authorization mechanism.

I can now have some resources controllers scoped to the type of users and I know that, thanks to the constraints at the route level, I will never have some authorization mishaps.
In more complex situations I can have only the show action for a user while having the whole resources actions for an admin.

Doing it like this let me not bother at all at the controller level and hence I don’t have the need to import any authorization package or code a complex custom authorization handler.

In the Router doc page, there is no mention at all of the word constraint.

So, do you know how I can do this?

Note: elixir being functional and Phoenix being based on plugs, I’m pretty sure that I can easily achieve this kind of constraint based routes, thanks to plugs, pipelines, forwarding routes and pattern matching, right? I just need some guidance.

Most Liked

LostKobrakai

LostKobrakai

The phoenix router can be so fast, because it works by harnessing elixir’s pattern matching. Therefore a certain url either matches to a path registered in the router or not. If you have multiple routes matching the first one will win. For the pattern match the only variables to match on are the path, host and method.

Given those constraints you cannot handle the logic you’re looking for in a phoenix router. This is not to say that you cannot nest your plug hierarchy more to get to the same resulting functionality. You could have one router match the path /dashboard, which forwards to a plug, which depending on your constraints decides which controller to call.

LostKobrakai

LostKobrakai

I was thinking about something akin to this.

# router
get  '/dashboard', ControllerByTypePlug, action: :show, controller: %{
  admin: Admin.DashboardController,
  user: User.DashboardController
}

# plug
defmodule MyApp.ControllerByTypePlug do
  import Plug.Conn

  def init(opts) do
    action = Keyword.fetch!(opts, :action)
    controller = Keyword.fetch!(opts, :controller)
    unless [:admin, :user] in Map.keys(controller) do
      raise "missing controller mapping"
    end
    %{action: action, controller: controller}
  end

  def call(conn, %{action: action, controller: controller}) do
    case type_to_atom(conn["session"]["user_type"]) do
      {:ok, type} -> 
        controller = Map.fetch!(controller, type)
        controller.call(conn, action)
      {:error, _} -> # handle error
    end
  end

  defp type_to_atom(str) when str in ["admin", "user"], do: {:ok, String.to_atom(str)}
  defp type_to_atom(str), do: {:error, str}
end

But if you can have separate routers per user type that’s even simpler and imho the even cleaner solution.

LostKobrakai

LostKobrakai

Phoenix might be constraint in what you can do directly on the router level, but on the otherhand as everything is a plug you can quite easily nest/switch out stuff.

# endpoint
plug :fetch_session # So session is available
# plug MyAppWeb.Router
plug MyAppWeb.Plug.DependantRouter

# This one forwards to the actual routers.
defmodule MyAppWeb.Plug.DependantRouter do
  import Plug.Conn

  def init(options), do: options

  def call(conn, _opts) do
    case conn["session"]["user_type"]
      "admin" -> AdminRouter.call(conn, [])
      "user" -> UserRouter.call(conn, [])
      ...
    end
  end
end

Where Next?

Popular in Questions Top

chokchit
** (DBConnection.ConnectionError) connection not available and request was dropped from queue after 2733ms. You can configure how long re...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
earth10
Hi, I’m just starting to build a side-project with Elixir and Phoenix and doing some basic test with Elixir alone. What strikes me is th...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call t...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New

Other popular topics Top

9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
boundedvariable
I am going through the kafka architecture. All the features what the kafka is providing are already in Erlang. I would like hear your opi...
New

Latest on Elixir Forum

We're in Beta

About us Mission Statement