I originally had a route defined like this :
get ':foo(/:bar)/:baz(/:qux)/:slug', to: 'application#show', constraints: { baz: /.+?\-\d+/ }, as: test
With this configuration, it works fine if I omit :bar in my path :
foo/baz-123/qux-quux/test
The controller returns the following parameters : {foo: 'foo', baz: 'baz-123', qux: 'qux-quux', slug: 'test'}
Now, the specs changed, and I need to accept alphanumeric characters in the baz constraint, so I did this :
get ':foo(/:bar)/:baz(/:qux)/:slug', to: 'application#show', constraints: { baz: /.+?\-[a-z0-9]+/ }, as: test
With this configuration, parameters mapping starts behaving differently. The same path foo/baz-123/qux-quux/test returns the following parameters in the controller :
{foo: 'foo', bar: 'baz-123', baz: 'qux-quux', slug: 'test'}
I thought that it's happening because qux-quux matches the constraint too, so I changed it to /.+?\-(?=.*\d+)[a-z0-9]+/ to ensure that the :baz parameter needs at least one digit in the second part after the -.
However, the controller still returns the same wrong mapping even if qux-quxx does not match the constraint.
How does Rails map the parameters in case of multiple optional segments ?
Your route path
:foo(/:bar)/:baz(/:qux)/:slugcontains 5 segments, 3 mandatory segments and 2 optional segments, so the request path will be validated iff::foo/:bar/:baz/:slug,:foo/:baz/:qux/:slug.When you set the constraint for a segment, the matcher will check whether the segment is matched with the constraint or not. As you can see with first 2 cases, the matcher know where the segment
:bazis, however in case of 4 segments, the matcher have to choose one of 2 valid cases, so it confuses, hence it will check segments one by one, and pick the last one matched.I guess the constraint segments are set higher priority than the others, hence they be checked first, after that the others is checked base on constraint segments position, in your case, after the segment
:bazis verified, the segment on its left hand will be the:barand the:slugsegment is on the right hand (the optional segment:quxwill be ignored).When you change the constraint to
/.+?\-(?=.*\d+)[a-z0-9]+/In my opinion, it's better to setup the constraint for the segment
:baralso if there's any different pattern between:barand:baz, so the matcher will check one by one and will not be confuse anymore.Another approach, i think, you could set the default value for the segment
:bar, and in your controller you could handle that default value, so now there's not any case the matcher will be confused.One more thing, your last regexp
/.+?\-(?=.*\d+)[a-z0-9]+/isgreedy, this will swallow all the path without respect the splash/, hence the pathfoo/bar/baz-x/testwill failed but the pathfoo/bar/baz-x/x1xwill passed since there's a digit after the character-. You could replace the greedy part(?=.*\d+)by(?=[^\/]*\d+)to fix it. So be careful with the regexp constraints.