Defining multiple routes with the same path using a different number of named parameters fails

Hello!

When defining multiple routes on the same controller and same action with different named parameters like:

get "/products/:foo", ProductController, :show
get "/products/:foo/:bar", ProductController, :show
get "/products/:foo/:bar/:rebar", ProductController, :show

I would like to be able to call them using the same path name and a different number of arguments.

The product_path function is clearly redefined multiple times but only the first definition is working properly.

iex(1)> MyappWeb.Router.Helpers.product_path
** (UndefinedFunctionError) function MyappWeb.Router.Helpers.product_path/0 is undefined or private. Did you mean one of:

      * product_path/3
      * product_path/4
      * product_path/5
      * product_path/6

    (myapp) MyappWeb.Router.Helpers.product_path()
iex(1)> MyappWeb.Router.Helpers.product_path(MyappWeb.Endpoint, :show, "1")
"/products/1"

iex(2)> MyappWeb.Router.Helpers.product_path(MyappWeb.Endpoint, :show, "1", "2")
** (Protocol.UndefinedError) protocol Enumerable not implemented for "2". This protocol is implemented for: DBConnection.PrepareStream, DBConnection.Stream, Date.Range, Ecto.Adapters.SQL.Stream, File.Stream, Function, GenEvent.Stream, HashDict, HashSet, IO.Stream, List, Map, MapSet, Postgrex.Stream, Range, Scrivener.Page, Stream, Timex.Interval

iex(2)> MyappWeb.Router.Helpers.product_path(MyappWeb.Endpoint, :show, "1", "2", "3")
** (Protocol.UndefinedError) protocol Enumerable not implemented for "3". This protocol is implemented for: DBConnection.PrepareStream, DBConnection.Stream, Date.Range, Ecto.Adapters.SQL.Stream, File.Stream, Function, GenEvent.Stream, HashDict, HashSet, IO.Stream, List, Map, MapSet, Postgrex.Stream, Range, Scrivener.Page, Stream, Timex.Interval

If it’s not possible to redefine the same route path on a different number of named argument, would it be possible to have a good warning message or a better exception?

In that case, why the path is still correctly redefined?

Thanks.

What if you define them in reverse order?

get "/products/:foo/:bar/:rebar", ProductController, :show
get "/products/:foo/:bar", ProductController, :show 
get "/products/:foo", ProductController, :show
2 Likes

When I define them in this order, all the routes are defined but only the first one is actually working:

iex(3)> h MyappWeb.Router.Helpers.product_path

      def product_path(conn_or_endpoint, action, foo)
            
      def product_path(conn_or_endpoint, action, foo, bar)

      def product_path(conn_or_endpoint, action, foo, bar, rebar)

      def product_path(conn_or_endpoint, action, foo, bar, rebar, params)

iex(4)> MyappWeb.Router.Helpers.product_path(MyappWeb.Endpoint, :show, "1")
** (Protocol.UndefinedError) protocol Phoenix.Param not implemented for []. This protocol is implemented for: Any, Atom, BitString, Integer, Map
    (phoenix) lib/phoenix/param.ex:121: Phoenix.Param.Any.to_param/1
    (dokkito) lib/dokkito_web/router.ex:1: MyappWeb.Router.Helpers.product_path/6
iex(4)> MyappWeb.Router.Helpers.product_path(MyappWeb.Endpoint, :show, "1", "2")
** (Protocol.UndefinedError) protocol Phoenix.Param not implemented for []. This protocol is implemented for: Any, Atom, BitString, Integer, Map
    (phoenix) lib/phoenix/param.ex:121: Phoenix.Param.Any.to_param/1
    (dokkito) lib/dokkito_web/router.ex:1: MyappWeb.Router.Helpers.product_path/6
iex(4)> MyappWeb.Router.Helpers.product_path(MyappWeb.Endpoint, :show, "1", "2", "3")
"/products/1/2/3"

You can define a single route, like this:

get "/products/:foo/:bar/:rebar", ProductController, :show

And solve this on your module:

def product_path(conn, params) do
...
bar =
  params["bar"] || nil
...
end
2 Likes

Yeah. But let’s say I want to localize and have:

get "/products/:foo/:bar/:rebar", ProductController, :show
get "/produits/:foo/:bar/:rebar", ProductController, :show

?

José Valim made an answer on my Github issue there:
https://github.com/phoenixframework/phoenix/issues/2864