Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optional routing parameters in Phoenix Framework

I've started to teach myself Phoenix Framework and their documentation is pretty good. I have however hit a stumbling block when it comes to specifying optional routing parameters. The Phoenix Framework Routing Documentation makes no mention of this as a feature, so I'm assuming the onus falls on the dev to come up with a solution.

I'll lay out my use case:

  1. User visits site at /page/test, a custom Plug then implements some code to find or assign a locale to the connection.
  2. Since there's no :locale parameter in the path, the default is used as per the line in my pipeline, which is plug HelloPhoenix.Plugs.Locale, "en".
  3. User visits site at /fr/page/test, and the same code gets executed in the pipeline, except time as the :locale parameter is present in the route, the custom Plug (HelloPhoenix.Plugs.Locale).

Now from a routing perspective, if I can't specify that the :locale parameter is optional, I end up with double the number of routes, e.g.:

scope "/", HelloPhoenix do
  use_pipeline :browser
  plug HelloPhoenix.Plugs.Locale, "en"

  # Route without locale
  get "/page/:slug", PageController, :show
  # Route with locale
  get "/:locale/page/:slug", PageController, :show
end

as you can tell, this could quickly become very arduous and repetitive without the ability to specify an optional routing parameter.

No I do have a workaround, which I'll post in an answer, but I'm not sure if it's (a) right, and (b) the easiest solution, since I'm new to Erlang, Elixir and Phoenix (I'm coming from a background in Ruby & PHP OOP).

like image 560
toomanyredirects Avatar asked Jul 01 '16 11:07

toomanyredirects


People also ask

What is a routing router in Phoenix?

Routers are the main hubs of Phoenix applications. They match HTTP requests to controller actions, wire up real-time channel handlers, and define a series of pipeline transformations scoped to a set of routes.

Can I add optional route parameters in Blazor?

Optional route parameters aren’t supported explicitly by Blazor, but the equivalent can be easily achieved by adding more than one @page declaration on a component. For example, alter the standard Counter.razor page to add an additional URL.

What is routing with parameters in sapui5 application?

We have seen complete process of Routing with Parameters in SAPUI5 Application, by using routing with parameters we can pass data from one view to another view, it is Hash based navigation which is used at the runtime to change the URL of the particular screen. Kindly leave any doubts or questions in the comments below.

What are the different types of parameter routing?

There are 5 types of Parameter routing 1.String (Hardcoded) parameter 2.Mandatory parameter 3.Optional parameter


2 Answers

You could have a simple plug like:

defmodule MyApp.SetLocale do

  @locales ~w{en fr}

  def init(opts), do: opts

  def call(conn, _opts) do
    case conn.path_info do
      [locale | rest] when locale in @locales ->
        %{conn | path_info: rest}
        |> Plug.Conn.assign(:locale, locale)
      _  -> Plug.Conn.assign(conn, :locale, "en")
    end
  end
end

Then place this plug before your router in endpoint.ex

  plug MyApp.SetLocale
  plug MyApp.Router
end

This way you can be confident the locale has been set before you even get to the router. You don't need to mention it in your router at all.

This technique will 404 if you enter a locale that is not in the @locales though.

like image 174
Gazler Avatar answered Oct 23 '22 15:10

Gazler


As mentioned in my question, I've come up with a solution which works in my case, but I'm not sure it's right or the easiest solution (especially if routing gets more complex)...

My solution uses Enum.each to loop over a list of prefixes, and then the routes only need to be specified once. This seems to work:

scope "/", HelloPhoenix do
  use_pipeline :browser
  plug HelloPhoenix.Plugs.Locale, "en"

  # Loop over list containing prefix :locale and no prefix.
  Enum.each ["/:locale", "/"], fn prefix ->
    # No need to duplicate routes
    get prefix <> "/page/:slug", PageController, :show
  end
end
like image 43
toomanyredirects Avatar answered Oct 23 '22 14:10

toomanyredirects