Rails路由可选段映射问题



我最初有一个这样定义的路由:

get ':foo(/:bar)/:baz(/:qux)/:slug', to: 'application#show', constraints: { baz: /.+?-d+/ }, as: test

在这个配置下,如果我在路径中省略:bar,它会工作得很好:foo/baz-123/qux-quux/test

控制器返回如下参数:{foo: 'foo', baz: 'baz-123', qux: 'qux-quux', slug: 'test'}

现在,规范改变了,我需要在baz约束中接受字母数字字符,所以我这样做:

get ':foo(/:bar)/:baz(/:qux)/:slug', to: 'application#show', constraints: { baz: /.+?-[a-z0-9]+/ }, as: test

使用此配置,参数映射开始表现不同。相同的路径foo/baz-123/qux-quux/test在控制器中返回以下参数:

{foo: 'foo', bar: 'baz-123', baz: 'qux-quux', slug: 'test'}

我认为它正在发生,因为qux-quux也匹配约束,所以我将其更改为/.+?-(?=.*d+)[a-z0-9]+/,以确保:baz参数在-之后的第二部分至少需要一个数字。

但是,即使qux-quxx不匹配约束,控制器仍然返回相同的错误映射。

在多个可选段的情况下,Rails如何映射参数?

您的路由路径:foo(/:bar)/:baz(/:qux)/:slug包含5个段,3个强制段和2个可选段,因此请求路径将在以下情况下进行验证:

  • 它包含足够的5段
  • 它包含三个片段,在这种情况下,显然这是三个强制部分
  • 它包含4段,这里有2个有效的情况::foo/:bar/:baz/:slug,:foo/:baz/:qux/:slug

当你为一个段设置约束时,匹配器将检查该段是否与约束匹配。正如你所看到的,在前2种情况下,匹配器知道段:baz在哪里,但是在4个段的情况下,匹配器必须从2个有效的情况中选择一个,所以它会混淆,因此它将逐个检查段,并选择最后一个匹配的。

/.+?-[a-z0-9]+/ =~ "baz-123" # matched
/.+?-[a-z0-9]+/ =~ "qux-quux" # matched <-- the last one
{foo: 'foo', bar: 'baz-123', baz: 'qux-quux', slug: 'test'}

我猜约束段的优先级高于其他段,因此首先检查它们,之后根据约束段的位置检查其他段,在您的情况下,在验证段:baz后,其左侧的段将是:bar,:slug段在右侧(可选段:qux将被忽略)。

当将约束更改为/.+?-(?=.*d+)[a-z0-9]+/

/.+?-(?=.*d+)[a-z0-9]+/ =~ "baz-123" # matched <-- baz
/.+?-(?=.*d+)[a-z0-9]+/ =~ "qux-quux" # not matched
# so the params should be (the `:bar` will be ignored)
{foo: 'foo', baz: 'baz-123', qux: 'qux-quux', slug: 'test'}

在我看来,如果:bar:baz之间有任何不同的模式,最好也为:bar段设置约束,这样匹配器将逐个检查,不会再混淆了。

get ':foo(/:bar)/:baz(/:qux)/:slug', to: 'application#show', constraints: { 
bar: /.*/,
baz: /.+?-(?=.*d+)[a-z0-9]+/ 
}, as: test

另一种方法,我认为,你可以为段:bar设置默认值,在你的控制器中你可以处理这个默认值,所以现在没有任何情况下匹配器会混淆。

还有一件事,您的最后一个regexp/.+?-(?=.*d+)[a-z0-9]+/greedy,这将吞噬所有路径而不尊重飞溅/,因此路径foo/bar/baz-x/test将失败,但路径foo/bar/baz-x/x1x将通过,因为字符-之后有一个数字。您可以将贪心部分(?=.*d+)替换为(?=[^/]*d+)来修复它。所以要小心使用regexp约束。