Reputation: 2002
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:
/page/test
, a custom Plug then implements some code to find or assign a locale
to the connection.:locale
parameter in the path, the default is used as per the line in my pipeline, which is plug HelloPhoenix.Plugs.Locale, "en"
./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).
Upvotes: 6
Views: 4569
Reputation: 84140
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.
Upvotes: 10
Reputation: 2002
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
Upvotes: 2