Ruby Rails 选择了错误的控制器操作

Ruby on Rails choosing wrong controller action

今天,我在 Rails 上遇到了一些奇怪的(而且非常不方便)Ruby 行为,即使持续梳理网络也没有得到令人满意的答案。 注意:我将方法和路由名称翻译成英文更容易阅读,希望我没有引入任何不一致的地方。

情况

环境

Ruby on Rails 4.2.0 在 Ruby 2.0 下执行(也在 Ruby 2.2.0 下测试)

相关代码

考虑具有以下操作的控制器,其中包括:

class AssignmentsController < ApplicationController
  def update
    ...
  end

  def takeover_confirmation
    ...
  end
end

routes.rb

因为我使用了很多手动定义的路由,所以我没有使用routes.rb中的资源。有问题的路由定义如下:

...
post 'assignments/:id' => 'assignments#update', as: 'assignment'
post 'assignments/takeover_confirmation' => 'assignments#takeover_confirmation'
...

rake routes的相关输出:

assignment POST  /assignments/:id(.:format)  assignments#update
assignments_takeover_confirmation  POST  /assignments/takeover_confirmation(.:format) assignments#takeover_confirmation

问题

当我对 assignments_takeover_confirmation_path 执行 POST 时,rails 将其路由到 update 方法。服务器日志:

Started POST "/assignments/takeover_confirmation" for ::1 at ...
Processing by AssignmentsController#update as HTML

缓解

如果我将 update 路由定义 放在 takeover_confirmation 之后 ,它会按预期工作(没有检查 POST update 虽然)。

此外,在写完所有这些之后,我发现我在 routes.rb 中对 update 方法使用了错误的请求类型(POST 而不是 PATCH)。在 routes.rb 中这样做确实解决了我的问题:

patch 'assignments/:id' => 'assignments#update', as: 'assignment'

然而,即使将其定义为 POST,Rails 也不应将对现有路径“/assignments/takeover_confirmation”的 POST 请求指向完全不同的操作,应该是? 我担心下次我为同一个控制器使用两条 POST 路由时,它会再次做同样的事情。

看来我对 Rails 路由有严重的误解,但我不能指手画脚...

编辑:解决方案

正如 katafrakt 解释的那样,上面对 /assignments/takeover_confirmation 的请求与路由 assignments/:id 匹配,因为 Rails 将 "takeover_confirmation" 部分解释为字符串并将其用于 :id 参数.因此,这是完全预期的行为。

工作示例

为了完整起见,这里是一个工作的(如果是简约的)路由定义,它应该按照 Chris 的评论的启发:

  resources :assignments do
    collection do
      post 'takeover_confirmation'
    end
  end

在这个例子中,只有我手动创建的路由被显式定义。 update、show 等路由(我最初手动定义的)现在由 resources: :assignments.

隐式定义

来自rake routes的相应摘录:

...
takeover_confirmation_assignments  POST  /assignments/takeover_confirmation(.:format) assignments#takeover_confirmation
...
assignment GET    /assignments/:id(.:format)  assignments#show
           PATCH  /assignments/:id(.:format)  assignments#update
           PUT    /assignments/:id(.:format)  assignments#update
           DELETE /assignments/:id(.:format)  assignments#destroy
....

感谢您的帮助!

However, even when defining it as POST, Rails should not direct a POST request to the existing path "/assignments/takeover_confirmation" to a completely different action, should it?

应该。 Rails 路由的匹配顺序与 routes.rb 文件中定义的顺序完全相同(从上到下)。因此,如果它匹配某个规则(并且 /assignments/takeover_confirmation 匹配 assignments/:id 规则),它将停止处理路由。

这种行为简单高效。我想任何类型的 "smart" 匹配最佳路线都会导致麻烦和意外的结果。

顺便说一句,这就是为什么过去常常在路由文件的最底部定义 catch-all 路由。