Search code examples
ruby-on-railsrubyroutesrails-routingglob

rails route mutiple globbing with optional segments


I have following pattern:

get "/sort/*sort_params/filter/*filter_params" => "order#show"

Which works OK! Link is getting parsed correctly and I receive something like that (Just an example):

Parameters: {"sort_params" => "price/asc", "filter_params" => "quantity/10"}

But I don't need both parameters all the time, so I've made both parts(sort and filter) optional:

get "(/sort/*sort_params)/(filter/*filter_params)" => "order#show"

But here I receive following:

Parameters: {"sort_parameters" => "price/asc/filter/quantity/10"}

So, it doesn't parse the final part properly and it gets in one line for some reasons.

If I remove parenthesis around the second part, like this:

get "(/sort/*sort_params)/filter/*filter_params" => "order#show"

Then it works totally OK (Apart from the fact that last part should be present, of course)!

I've tried placing parenthesis in different places, but no luck. Can someone help me out with this one ?


Solution

  • There's interesting stuff going on behind the scenes. Here's the representation of your Rails route I get on a fresh app (Rails 4.1):

    (/sort/*sort_params)(/filter/*filter_params)(.:format)

    You've made both arguments optional, and the only separator left is / between them. And it gets discarded: while parsing the route is split into groups like /thing, and your lonely / doesn't fit in.

    So actions are: it sees /sort and tosses the rest into the args, done. Globbing is dangerous alright. But if it doesn't, it checks for the /filter, and tosses the rest in its args. Therefore, it interprets paths with only filter or sort. Not both of them.

    I can say for sure that using scopes here is what others call the Rails way. It's good and its use is encouraged. But if you're interested in repairing your one-line solution, I suggest you put a better delimiter, that fits in to that /thing shape. Like, say, /list.

    (/sort/*sort_params)/list(/filter/*filter_params)

    Of course, that means you'll always need to put it into your URL. Here are some examples of what it will parse correctly:

    /sort/asc/list

    /sort/asc/list/filter/quantity/10

    /list/filter/alright

    /list

    That's the simplest fix I can imagine. Others I can imagine as well make that route insanely long and involve direct declaration of how many args you're going to have here and there.

    But this is compact and looks fine to me. However, all this is research. I recommend using scopes. Complex matches like this are harder to maintain consistent when other components get added.