Can I use regular expressions in my routes?

911 views
Skip to first unread message

Dan Q

unread,
May 17, 2016, 7:44:52 AM5/17/16
to phoenix-talk
Hi all. I'm new to Phoenix and Elixir, and I've spent the morning scouring documentation and source and doing experiments, but I'm completely stuck. What I'd like to be able to do is to set conditions that apply to my routes, ideally using regular expressions. 

What I'd like to be able to do is something like this:


get "/:id", SomeController, :show where Regex.match?(~r/^\d{5}$/, id)
get "/:id", OtherController, :show where Regex.match?(~r/^A\d{4}$/, id)


That is - where a named component of the URL matches a regular expression, that route is used. In Rails, my regular framework, this would be expressed as:


get '/:id', to: 'some#show', id: /\d{5}/
get '/:id', to: 'other#show', id: /A\d{4}/


But I can't find a Phoenix (well, Plug) equivalent, and my Elixir's still a little weak for me to know where to get started writing my own.


Any suggestions? Thanks!


Chris McCord

unread,
May 17, 2016, 9:49:52 AM5/17/16
to phoeni...@googlegroups.com
The answer, when in doubt, is almost always "use a plug!" :)

You can't match on regex in a route like Rails because it is extremely slow. It would cause us to have to walk the routes on every request and test one-by-one. Instead, we optimism for the 95% usecase and precompile the route definitions to function clause matches. For the other 5% cases, you can use custom plugs that are suited exactly for your needs. For example, you can do what you want with this:

defmodule MyApp.Router do

  scope "/", MyApp do
    get "/:id", SlugRouter, :show
    get "/:id", OtherController, :show
  end
end

defmodule MyApp.SlugRouter do
  import Plug.Conn

  def init(opts), do: opts

  def call(%Plug.Conn{params: %{"id" => id}} = conn, action) do
    cond do
      Regex.match?(~r/^\d{5}$/, id) ->
        SomeController.call(conn, SomeController.init(action))
      Regex.match?(~r/^A\d{4}$/, id) ->
        OtherController.call(conn, OtherController.init(action))
      true -> conn
    end
  end
end


Hope that helps

–Chris
--
You received this message because you are subscribed to the Google Groups "phoenix-talk" group.
To unsubscribe from this group and stop receiving emails from it, send an email to phoenix-talk...@googlegroups.com.
To post to this group, send email to phoeni...@googlegroups.com.
Visit this group at https://groups.google.com/group/phoenix-talk.
To view this discussion on the web visit https://groups.google.com/d/msgid/phoenix-talk/f1453e15-178e-4e3a-acb4-3c137f00c5b7%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Jamil Abreu

unread,
Dec 14, 2017, 11:53:03 AM12/14/17
to phoenix-talk
Hi! In Phoenix 1.3 there seems to be an error related to the `true -> conn` part above. 

I'm getting `** (Plug.Conn.NotSentError) a response was neither set nor sent from the connection` on the route, and `unused import Plug.Conn` in the console.

It works when adding another direct call: `true -> SomeOtherController.call(conn, SomeOtherController.init(action))` but not with just `true -> conn`

Any ideas?

Chris McCord

unread,
Dec 14, 2017, 11:55:08 AM12/14/17
to phoeni...@googlegroups.com
You need to send a respond to the client one way or the other. If this plug is the last plug in your stack that may or may not send a response, you need to send a response from here. Perhaps your fall through case should either send a no content response or you should 404?

Jamil Abreu

unread,
Dec 14, 2017, 12:07:24 PM12/14/17
to phoenix-talk
My setup is similar to your example, where I expected `get "/:id", OtherController, :show` to catch any routes not handled by `get "/:id", SlugRouter, :show`.

Is a response still needed in this case? In my head I expected the conn to "continue on" down the list of routes to look for a match if not caught by SlugRouter. :thinking emoji: Appreciate this!!

  scope "/", MyApp do
    get "/:id", SlugRouter, :show
    get "/:id", OtherController, :show
  end



Chris McCord

unread,
Dec 14, 2017, 12:11:03 PM12/14/17
to phoeni...@googlegroups.com
Your plugs downstream of the router should still continue as normal, like you are guessing, but you need to make sure one of them sends a response, which looks like it’s not happening. So to be clear, it may be fine for your router to return the `conn` untouched, but in that case you need to ensure that some other plug/router downstream sends a response. Have you verified that?

Jamil Abreu

unread,
Dec 14, 2017, 12:17:22 PM12/14/17
to phoenix-talk
Gotcha! Yes, I verified that by removing my SlugRouter and confirming that OtherController picks up the route and sends a response as expected.

Jamil Abreu

unread,
Dec 14, 2017, 3:23:46 PM12/14/17
to phoenix-talk
Here's a repo where I recreate what I'm seeing: 

https://github.com/jamilabreu/regex_routing


On Thursday, December 14, 2017 at 12:11:03 PM UTC-5, Chris McCord wrote:
Reply all
Reply to author
Forward
0 new messages