Please help me understand router philosophy

Here is my router.ex:

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :protected do
    plug Pow.Plug.RequireAuthenticated,
      error_handler: Pow.Phoenix.PlugErrorHandler
  end

# ...
  
  scope "/cms", HelloWeb.CMS, as: :cms do
    pipe_through [:browser]
    resources "/articles", ArticleController, only: [:index, :show]
    resources "/categories", CategoryController, only: [:index, :show]
  end
  
  scope "/cms", HelloWeb.CMS, as: :cms do
    pipe_through [:browser, :protected]
    resources "/articles", ArticleController, except: [:index, :show]
    resources "/categories", CategoryController, except: [:index, :show]
  end

After calling:
http://localhost:4000/cms/articles/new

I got error like this (only important parts):

##### Ecto.Query.CastError <small>at GET</small> <small>/cms/articles/new</small>

# deps/ecto/lib/ecto/repo/queryable.ex:373: value `"new"` in `where` cannot be cast to type :id in query:

from a0 in Hello.CMS.Article, where: a0.id == ^"new", select: a0


Params

id

    "new"

And error in controller:

  def show(conn, %{"id" => id}) do

    article = CMS.get_article!(id)

    render(conn, "show.html", article: article)

  end

I can’t understand, why “new” is calling “show”.

Because the generated routes are

get "/articles", ArticleController, :index
get "/articles/:id", ArticleController, :show

So GET /articles/new matches the :show route, with id set to "new".

2 Likes

I swapped scopes protected with unprotected and now:

"/articles/new"
"/articles/:id/edit"
"/articles"
"/articles/:id"

And it works as expected