OmarGoubail

OmarGoubail

Different subdomains for admins and users

Hello, I am working on an app and I want to separate admin and user routes to different subdomains, I found an article how-to-serve-multiple-domains-in-a-single-phoenix-app It got me part of the way there, but for some reason I can’t get it working the same way for example in the article:

scope "/", MyAppWeb, host: "music." do
  live "/", MusicLive
end

# partial host match - match subdomain `video.`, i.e., matches `video.myapp.com`
scope "/", MyAppWeb, host: "video." do
  live "/", VideoLive
end

# partial host match - match subdomain `admin.`, i.e., matches `admin.myapp.com`
scope "/", MyAppWeb, host: "admin." do
  live "/", AdminLive, :home
  live "/settings", AdminSettingsLive # admin.myapp.com/settings
end

They used “/” in different subdomains, however when I tried to do the same thing, I get a compiler warning this clause cannot match because a previous clause at line 30 always matchesElixir.

  # Public routes (no authentication required)
  scope "/", AvocatoxWeb do
    pipe_through :browser

    # Add other public routes here
    live "/", Home
  end

  # Admin routes (authentication required)
  scope "/", AvocatoxWeb, host: "admin." do
    pipe_through [:browser, :admin]

    live "/", Admin.Home
  end

It works when I go to http://localhost:4000 and http://admin.localhost:4000, but I get the same home function which is the first one.

I mean instead of this markup in the admin.


 def render(assigns) do
    ~H"""
    <div>
      <h1>Admin Home</h1>
    </div>
    """
  end

I get this

 def render(assigns) do
    ~H"""
    <div>
      <h1>Home</h1>
    </div>
    """
  end

I am new to elixir and phoenix and I am enjoying them a lot! Hopefully this is just a gap in my understanding. Thanks for your help in Advance.

Marked As Solved

dpreston

dpreston

Try moving your default scope (without a :host) to the end of your list of scopes. Or, at least, after any scopes that have matching paths.

Also Liked

kip

kip

ex_cldr Core Team

It worked! But I am not sure I understand why.

@OmarGoubail, Maybe this will help clarify a little bit:

  1. No matter what DSL you use (like the Plug DSL), eventually everything compiles down to functions.
  2. In Elixir (and all BEAM languages), a function is differentiated by both its name and its arity (arity being how many parameters it takes). You see this commonly written as foo/1 or foo/3 where /1 and /3 is the arity meaning a function with one param, and a function with three params. In Elixir these are different functions.
  3. Pattern matching means that you can write multiple function bodies that have the same name and the same arity. They are then differentiated by pattern matching. This is how the scope macro resolves to a set of functions with the same name and arity - but different pattern matches.

If that clear so far (and by all means comment if it is not clear) then we can now pay our attention to the runtime environment. Lets say we have the following (and this is very unlikely to be what scope actually compiles to, but ultimately it does compile to something like this):

# Called for any request since there is no pattern matching
def plug(MyPlug, conn, scheme, host, path, query) do
  ...
end

# Called if the host is "admin."
def plug(MyPlug, conn, scheme, "admin.", path, query) do
  ...
end

How does the runtime know which of these function bodies to invoke? It checks each function in turn - in lexical order as written in your code.

In our example if will first check def plug(:my_plug, conn, scheme, host, path, query). Since the only pattern match here is on the plug name, it will match. And this is the function body that will run.

This is basically the source of the this clause cannot match error. The first function body matches everything so it will always be invoked.

By moving the “default” function body to the end of the list of function bodies, it will only be invoked if all other function bodies don’t match. Using the example above, we can see that the plug(:my_plug, conn, scheme, "admin.", path, query) will match if the host is admin. And therefore that function body will be invoked, not the default one.

BartOtten

BartOtten

Because the route configuration you see is being rewritten to functions, subject to pattern matching. When no ‘:host’ is given, the argument will be a wildcard match.

Example

get(_host, /), do: non-admin
get(“admin.”, /), do: admin

Now when you visit “admin.domain.com/“ it will match the first function head. That is not what you want.

Changing the order makes the wildcard match come last, which is what you want.

OmarGoubail

OmarGoubail

I am not sure I understand completely what you mean, but I did write an ensure_admin plug, which doesn’t allow normal users to log in only admins that have an account with the role admin.

  pipeline :admin do
    plug :require_authenticated_user
    plug AvocatoxWeb.Plugs.EnsureAdmin
  end

I am handling different cases as I go but I think this is secure, no?

Where Next?

Popular in Questions Top

sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
JulienCorb
I am trying to implement my new.html.eex file to create new posts on my website. new.html.eex: &lt;h1&gt;Create Post&lt;/h1&gt; &lt;%= ...
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
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

Other popular topics Top

danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 29377 241
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 41539 114
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 52341 488
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
AstonJ
Please see the new poll here: Which code editor or IDE do you use? (Poll) (2022 Edition) It’s been a while since we first asked this, I...
208 31142 143
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New

We're in Beta

About us Mission Statement