更多内容请访问 rubyonrails.org:

Rails 路由从外到内

本指南涵盖了 Rails 路由的用户界面功能。

阅读本指南后,您将了解

  • 如何解释 config/routes.rb 中的代码。
  • 如何使用首选的资源式风格或 match 方法来构建自己的路由。
  • 如何声明路由参数,这些参数会传递给控制器动作。
  • 如何使用路由助手自动创建路径和 URL。
  • 高级技术,例如创建约束和挂载 Rack 端点。

1. Rails 路由器的作用

Rails 路由器根据 URL 路径,将传入的 HTTP 请求与 Rails 应用程序中特定的控制器动作匹配。(它还可以转发到 Rack 应用程序。)路由器还会根据路由器中配置的资源生成路径和 URL 助手。

1.1. 将传入 URL 路由到代码

当 Rails 应用程序收到传入请求时,它会要求路由器将其与控制器动作(又称方法)匹配。例如,以下传入请求:

GET /users/17

如果第一个匹配的路由是

get "/users/:id", to: "users#show"

该请求与 UsersController 类的 show 动作匹配,并且 params 哈希中包含 { id: '17' }

当传入字符串时,to: 选项需要 controller#action 格式。或者,你可以传入一个符号并使用 action: 选项,而不是 to:。你也可以传入不带 # 的字符串,在这种情况下,将使用 controller: 选项而不是 to:。例如:

get "/users/:id", controller: "users", action: :show

Rails 在指定路由时,控制器名称使用蛇形命名法(snake_case)。例如,如果你的控制器名为 UserProfilesController,则会指定到 show 动作的路由为 user_profiles#show

1.2. 从代码生成路径和 URL

路由器会自动为你的应用程序生成路径和 URL 助手方法。使用这些方法,你可以避免硬编码路径和 URL 字符串。

例如,当定义以下路由时,可以使用 user_pathuser_url 助手方法:

get "/users/:id", to: "users#show", as: "user"

as: 选项用于为路由提供自定义名称,该名称用于生成 URL 和路径助手。

假设您的应用程序在控制器中包含此代码

@user = User.find(params[:id])

以及在相应视图中的此代码

<%= link_to 'User Record', user_path(@user) %>

路由器将从 user_path(@user) 生成路径 /users/17。使用 user_path 助手可以避免在视图中硬编码路径。如果最终将路由移动到不同的 URL,这会很有帮助,因为您无需更新相应的视图。

它还会生成 user_url,其目的相似。虽然 user_path 生成相对 URL,例如 /users/17,但 user_url 在上述示例中生成绝对 URL,例如 https://example.com/users/17

1.3. 配置 Rails 路由器

路由位于 config/routes.rb 中。以下是一个典型 Rails 应用程序中路由的示例。接下来的部分将解释此文件中使用的不同路由助手。

Rails.application.routes.draw do
  resources :brands, only: [:index, :show] do
    resources :products, only: [:index, :show]
  end

  resource :basket, only: [:show, :update, :destroy]

  resolve("Basket") { route_for(:basket) }
end

由于这是一个普通的 Ruby 源文件,你可以使用 Ruby 的所有功能(例如条件语句和循环)来帮助你定义路由。

包裹您的路由定义的 Rails.application.routes.draw do ... end 块是必需的,它用于为路由器 DSL (领域特定语言) 建立作用域,并且不能删除。

请注意 routes.rb 中的变量名,因为它们可能与路由器的 DSL 方法发生冲突。

2. 资源路由:Rails 默认设置

资源路由允许您快速声明给定资源控制器的所有常用路由。例如,只需调用一次 resources,即可声明 indexshowneweditcreateupdatedestroy 动作的所有必要路由,而无需单独声明每个路由。

2.1. Web 上的资源

浏览器通过使用特定的 HTTP 动词(例如 GETPOSTPATCHPUTDELETE)请求 URL 来从 Rails 请求页面。每个 HTTP 动词都是对资源执行操作的请求。资源路由将相关请求映射到单个控制器中的动作。

当您的 Rails 应用程序收到以下传入请求时:

DELETE /photos/17

它会要求路由器将其映射到控制器动作。如果第一个匹配的路由是:

resources :photos

Rails 将把该请求分派给 PhotosControllerdestroy 动作,并在 params 中包含 { id: '17' }

2.2. CRUD、动词和动作

在 Rails 中,资源路由提供了从传入请求(HTTP 动词 + URL 的组合)到控制器动作的映射。按照约定,每个动作通常映射到数据上的特定 CRUD 操作。路由文件中的单个条目,例如

resources :photos

在您的应用程序中创建了七个不同的路由,所有这些路由都映射到 PhotosController 动作

HTTP 动词 路径 Controller#Action 用于
GET /photos photos#index 显示所有照片的列表
GET /photos/new photos#new 返回用于创建新照片的 HTML 表单
POST /photos photos#create 创建新照片
GET /photos/:id photos#show 显示特定照片
GET /photos/:id/edit photos#edit 返回用于编辑照片的 HTML 表单
PATCH/PUT /photos/:id photos#update 更新特定照片
DELETE /photos/:id photos#destroy 删除特定照片

由于路由器使用 HTTP 动词和路径来匹配入站请求,因此四个 URL 可以映射到七个不同的控制器动作。例如,当动词为 GET 时,相同的 photos/ 路径匹配 photos#index,当动词为 POST 时,匹配 photos#create

routes.rb 文件中的顺序很重要。Rails 路由按照它们指定的顺序进行匹配。例如,如果你有一个 resources :photosget 'photos/poll' 之上,那么 resources 行的 show 动作路由将优先于 get 行匹配。如果你希望 photos/poll 路由优先匹配,你需要将 get 行移动到 resources 行之**上**。

2.3. 路径和 URL 助手

创建资源路由还将向应用程序中的控制器和视图公开许多助手。

例如,将 resources :photos 添加到路由文件将生成这些 _path 助手:

路径助手 返回 URL
photos_path /photos
new_photo_path /photos/new
edit_photo_path(:id) /photos/:id/edit`
photo_path(:id) /photos/:id

传递给路径助手的参数(如上文的 :id)会传递给生成的 URL,例如 edit_photo_path(10) 将返回 /photos/10/edit

这些 _path 助手中的每一个都有一个对应的 _url 助手(例如 photos_url),它返回相同路径,但以当前主机、端口和路径前缀为前缀。

在“_path”和“_url”之前使用的前缀是路由名称,可以通过查看 bin/rails routes 命令输出的“prefix”列来识别。要了解更多信息,请参阅下面的列出现有路由

2.4. 同时定义多个资源

如果您需要为多个资源创建路由,可以通过一次调用 resources 来节省一些打字工作:

resources :photos, :books, :videos

以上是以下代码的快捷方式

resources :photos
resources :books
resources :videos

2.5. 单数资源

有时,您有一个用户期望只有一个的资源(即,没有 index 动作来列出该资源的所有值是没有意义的)。在这种情况下,您可以使用 resource(单数)而不是 resources

以下资源路由在您的应用程序中创建了六个路由,所有路由都映射到 Geocoders 控制器

resource :geocoder
resolve("Geocoder") { [:geocoder] }

调用 resolve 对于通过记录识别Geocoder 实例转换为单数路由是必要的。

以下是为单一资源创建的所有路由

HTTP 动词 路径 Controller#Action 用于
GET /geocoder/new geocoders#new 返回用于创建地理编码器的 HTML 表单
POST /geocoder geocoders#create 创建新的地理编码器
GET /geocoder geocoders#show 显示唯一的地理编码器资源
GET /geocoder/edit geocoders#edit 返回用于编辑地理编码器的 HTML 表单
PATCH/PUT /geocoder geocoders#update 更新唯一的地理编码器资源
DELETE /geocoder geocoders#destroy 删除地理编码器资源

单数资源映射到复数控制器。例如,geocoder 资源映射到 GeocodersController

单数资源路由生成以下助手

  • new_geocoder_path 返回 /geocoder/new
  • edit_geocoder_path 返回 /geocoder/edit
  • geocoder_path 返回 /geocoder

与复数资源一样,以 _url 结尾的相同助手也将包含主机、端口和路径前缀。

2.6. 控制器命名空间和路由

在大型应用程序中,您可能希望将控制器组组织在命名空间下。例如,您可能在 Admin:: 命名空间下有许多控制器,这些控制器位于 app/controllers/admin 目录中。您可以使用 namespace 块来路由到这样的组:

namespace :admin do
  resources :articles
end

对于 Admin::ArticlesController,Rails 将创建以下路由

HTTP 动词 路径 Controller#Action 命名路由助手
GET /admin/articles admin/articles#index admin_articles_path
GET /admin/articles/new admin/articles#new new_admin_article_path
POST /admin/articles admin/articles#create admin_articles_path
GET /admin/articles/:id admin/articles#show admin_article_path(:id)
GET /admin/articles/:id/edit admin/articles#edit edit_admin_article_path(:id)
PATCH/PUT /admin/articles/:id admin/articles#update admin_article_path(:id)
DELETE /admin/articles/:id admin/articles#destroy admin_article_path(:id)

请注意,在上面的示例中,所有路径都具有 /admin 前缀,这符合 namespace 的默认约定。

2.6.1. 使用模块

如果您想将 /articles(不带前缀 /admin)路由到 Admin::ArticlesController,您可以使用 scope 块指定模块:

scope module: "admin" do
  resources :articles
end

另一种写法是:

resources :articles, module: "admin"

2.6.2. 使用作用域

或者,您也可以将 /admin/articles 路由到 ArticlesController(不带 Admin:: 模块前缀)。您可以使用 scope 块指定路径:

scope "/admin" do
  resources :articles
end

另一种写法是:

resources :articles, path: "/admin/articles"

对于这些替代方案(路径中不带 /admin,模块前缀中不带 Admin::),命名路由助手与不使用 scope 时相同。

在最后一种情况下,以下路径映射到 ArticlesController

HTTP 动词 路径 Controller#Action 命名路由助手
GET /admin/articles articles#index articles_path
GET /admin/articles/new articles#new new_article_path
POST /admin/articles articles#create articles_path
GET /admin/articles/:id articles#show article_path(:id)
GET /admin/articles/:id/edit articles#edit edit_article_path(:id)
PATCH/PUT /admin/articles/:id articles#update article_path(:id)
DELETE /admin/articles/:id articles#destroy article_path(:id)

如果您需要在 namespace 块内使用不同的控制器命名空间,可以指定绝对控制器路径,例如:get '/foo', to: '/foo#index'

2.7. 嵌套资源

拥有逻辑上是其他资源的子资源的资源是很常见的。例如,假设您的应用程序包含以下模型:

class Magazine < ApplicationRecord
  has_many :ads
end

class Ad < ApplicationRecord
  belongs_to :magazine
end

嵌套路由声明允许您在路由中捕获此关系

resources :magazines do
  resources :ads
end

除了杂志的路由之外,此声明还会将广告路由到 AdsController。以下是嵌套 ads 资源的所有路由:

HTTP 动词 路径 Controller#Action 用于
GET /magazines/:magazine_id/ads ads#index 显示特定杂志的所有广告列表
GET /magazines/:magazine_id/ads/new ads#new 返回用于创建属于特定杂志的新广告的 HTML 表单
POST /magazines/:magazine_id/ads ads#create 创建属于特定杂志的新广告
GET /magazines/:magazine_id/ads/:id ads#show 显示属于特定杂志的特定广告
GET /magazines/:magazine_id/ads/:id/edit ads#edit 返回用于编辑属于特定杂志的广告的 HTML 表单
PATCH/PUT /magazines/:magazine_id/ads/:id ads#update 更新属于特定杂志的特定广告
DELETE /magazines/:magazine_id/ads/:id ads#destroy 删除属于特定杂志的特定广告

这还将创建常见的路径和 URL 路由助手,例如 magazine_ads_urledit_magazine_ad_path。由于 ads 资源嵌套在 magazines 下,因此广告 URL 需要杂志。助手可以将 Magazine 实例作为第一个参数 (edit_magazine_ad_path(@magazine, @ad))。

2.7.1. 嵌套限制

您可以根据需要将资源嵌套在其他嵌套资源中。例如

resources :publishers do
  resources :magazines do
    resources :photos
  end
end

在上面的例子中,应用程序将识别诸如以下路径

/publishers/1/magazines/2/photos/3

相应的路由助手将是 publisher_magazine_photo_url,需要您指定所有三个级别的对象。正如您所见,深度嵌套的资源可能变得过于复杂和难以维护。

一般经验法则是只嵌套资源 1 级深。

2.7.2. 浅层嵌套

避免深层嵌套(如上所述推荐)的一种方法是生成在父级下作用域的集合动作 - 以获得层次结构感,但不嵌套成员动作。换句话说,只构建具有唯一标识资源所需最少量信息的路由。

“成员”动作是适用于单个资源并需要 ID 来标识它们所作用的特定资源的动作,例如 showedit 等。“集合”动作是作用于整个资源集的动作,例如 index

例如

resources :articles do
  resources :comments, only: [:index, :new, :create]
end
resources :comments, only: [:show, :edit, :update, :destroy]

上面我们使用 :only 选项,它告诉 Rails 只创建指定的路由。这个想法在描述性路由和深层嵌套之间取得了平衡。有一个通过 :shallow 选项实现此目的的简写语法

resources :articles do
  resources :comments, shallow: true
end

这将生成与第一个示例完全相同的路由。您也可以在父资源中指定 :shallow 选项,在这种情况下,所有嵌套资源都将是浅层的

resources :articles, shallow: true do
  resources :comments
  resources :quotes
end

上面的 articles 资源将生成以下路由

HTTP 动词 路径 Controller#Action 命名路由助手
GET /articles/:article_id/comments(.:format) comments#index article_comments_path
POST /articles/:article_id/comments(.:format) comments#create article_comments_path
GET /articles/:article_id/comments/new(.:format) comments#new new_article_comment_path
GET /comments/:id/edit(.:format) comments#edit edit_comment_path
GET /comments/:id(.:format) comments#show comment_path
PATCH/PUT /comments/:id(.:format) comments#update comment_path
DELETE /comments/:id(.:format) comments#destroy comment_path
GET /articles/:article_id/quotes(.:format) quotes#index article_quotes_path
POST /articles/:article_id/quotes(.:format) quotes#create article_quotes_path
GET /articles/:article_id/quotes/new(.:format) quotes#new new_article_quote_path
GET /quotes/:id/edit(.:format) quotes#edit edit_quote_path
GET /quotes/:id(.:format) quotes#show quote_path
PATCH/PUT /quotes/:id(.:format) quotes#update quote_path
DELETE /quotes/:id(.:format) quotes#destroy quote_path
GET /articles(.:format) articles#index articles_path
POST /articles(.:format) articles#create articles_path
GET /articles/new(.:format) articles#new new_article_path
GET /articles/:id/edit(.:format) articles#edit edit_article_path
GET /articles/:id(.:format) articles#show article_path
PATCH/PUT /articles/:id(.:format) articles#update article_path
DELETE /articles/:id(.:format) articles#destroy article_path

shallow 方法与块一起使用,创建了一个作用域,在该作用域内,每个嵌套都是浅层的。这会生成与上一个示例相同的路由

shallow do
  resources :articles do
    resources :comments
    resources :quotes
  end
end

scope 可以与两个选项一起使用以自定义浅层路由 - :shallow_path:shallow_prefix

shallow_path 选项为成员路径添加指定参数作为前缀

scope shallow_path: "sekret" do
  resources :articles do
    resources :comments, shallow: true
  end
end

这里的 comments 资源将为其生成以下路由

HTTP 动词 路径 Controller#Action 命名路由助手
GET /articles/:article_id/comments(.:format) comments#index article_comments_path
POST /articles/:article_id/comments(.:format) comments#create article_comments_path
GET /articles/:article_id/comments/new(.:format) comments#new new_article_comment_path
GET /sekret/comments/:id/edit(.:format) comments#edit edit_comment_path
GET /sekret/comments/:id(.:format) comments#show comment_path
PATCH/PUT /sekret/comments/:id(.:format) comments#update comment_path
DELETE /sekret/comments/:id(.:format) comments#destroy comment_path

:shallow_prefix 选项将指定参数添加到 _path_url 路由助手

scope shallow_prefix: "sekret" do
  resources :articles do
    resources :comments, shallow: true
  end
end

这里的 comments 资源将为其生成以下路由

HTTP 动词 路径 Controller#Action 命名路由助手
GET /articles/:article_id/comments(.:format) comments#index article_comments_path
POST /articles/:article_id/comments(.:format) comments#create article_comments_path
GET /articles/:article_id/comments/new(.:format) comments#new new_article_comment_path
GET /comments/:id/edit(.:format) comments#edit edit_sekret_comment_path
GET /comments/:id(.:format) comments#show sekret_comment_path
PATCH/PUT /comments/:id(.:format) comments#update sekret_comment_path
DELETE /comments/:id(.:format) comments#destroy sekret_comment_path

2.8. 路由关注点

路由关注点允许您声明可以在其他资源中重用的通用路由。要定义关注点,请使用 concern

concern :commentable do
  resources :comments
end

concern :image_attachable do
  resources :images, only: :index
end

这些关注点可以在资源中使用,以避免代码重复并在路由之间共享行为

resources :messages, concerns: :commentable

resources :articles, concerns: [:commentable, :image_attachable]

以上等同于

resources :messages do
  resources :comments
end

resources :articles do
  resources :comments
  resources :images, only: :index
end

您还可以在 scopenamespace 块中调用 concerns 以获得与上面相同的结果。例如:

namespace :messages do
  concerns :commentable
end

namespace :articles do
  concerns :commentable
  concerns :image_attachable
end

2.9. 从对象创建路径和 URL

除了使用路由助手之外,Rails 还可以从参数数组创建路径和 URL。例如,假设您有这样一组路由

resources :magazines do
  resources :ads
end

使用 magazine_ad_path 时,您可以传入 MagazineAd 的实例,而不是数字 ID

<%= link_to 'Ad details', magazine_ad_path(@magazine, @ad) %>

生成的路径将类似于 /magazines/5/ads/42

您也可以使用 url_for 与对象数组来获取上述路径,如下所示

<%= link_to 'Ad details', url_for([@magazine, @ad]) %>

在这种情况下,Rails 将看到 @magazine 是一个 Magazine@ad 是一个 Ad,因此将使用 magazine_ad_path 助手。编写该 link_to 的更短方法是只指定对象而不是完整的 url_for 调用

<%= link_to 'Ad details', [@magazine, @ad] %>

如果你只想链接到一本杂志

<%= link_to 'Magazine details', @magazine %>

对于其他操作,您需要将操作名称作为数组的第一个元素插入,例如 edit_magazine_ad_path

<%= link_to 'Edit Ad', [:edit, @magazine, @ad] %>

这允许您将模型实例视为 URL,是使用资源式风格的一个关键优势。

为了自动从诸如 [@magazine, @ad] 之类的对象派生路径和 URL,Rails 使用 ActiveModel::NamingActiveModel::Conversion 模块中的方法。具体来说,@magazine.model_name.route_key 返回 magazines@magazine.to_param 返回模型 id 的字符串表示。因此,对于对象 [@magazine, @ad],生成的路径可能类似于 /magazines/1/ads/42

2.10. 添加更多 RESTful 路由

您不限于 RESTful 路由默认创建的七个路由。您可以添加适用于集合或集合中单个成员的其他路由。

以下部分描述了添加成员路由和集合路由。术语 member 指的是作用于单个元素的路由,例如 showupdatedestroy。术语 collection 指的是作用于多个或一组元素的路由,例如 index 路由。

2.10.1. 添加成员路由

您可以像这样在资源块中添加一个 member

resources :photos do
  member do
    get "preview"
  end
end

/photos/1/preview 的传入 GET 请求将路由到 PhotosControllerpreview 动作。资源 ID 值将在 params[:id] 中可用。它还将创建 preview_photo_urlpreview_photo_path 助手。

member 块中,每个路由定义都指定 HTTP 动词(在上面的示例中,get 'preview' 使用 get)。除了 get,您还可以使用 patchputpostdelete

如果你没有多个 member 路由,你也可以将 :on 传递给路由,从而省去该块

resources :photos do
  get "preview", on: :member
end

你也可以省略 :on 选项,这将创建相同的成员路由,只不过资源 ID 值将分别在 params[:photo_id] 而不是 params[:id] 中可用。路由助手也将从 preview_photo_urlpreview_photo_path 重命名为 photo_preview_urlphoto_preview_path

2.10.2. 添加集合路由

要向集合添加路由,请使用 collection 块:

resources :photos do
  collection do
    get "search"
  end
end

这将使 Rails 能够识别诸如 /photos/search(使用 GET 请求)的路径,并路由到 PhotosControllersearch 动作。它还将创建 search_photos_urlsearch_photos_path 路由助手。

就像成员路由一样,你可以将 :on 传递给路由

resources :photos do
  get "search", on: :collection
end

如果您将符号作为第一个位置参数定义其他资源路由,请注意它与使用字符串不等价。符号推断控制器动作,而字符串推断路径。

2.10.3. 为其他新建动作添加路由

要使用 :on 快捷方式添加备用新建操作

resources :comments do
  get "preview", on: :new
end

这将使 Rails 能够识别诸如 /comments/new/preview(使用 GET 请求)的路径,并路由到 CommentsControllerpreview 动作。它还将创建 preview_new_comment_urlpreview_new_comment_path 路由助手。

如果您发现自己正在为资源路由添加许多额外的操作,那么是时候停下来问自己是否正在掩盖另一个资源的存在了。

可以自定义 resources 生成的默认路由和助手,更多信息请参阅自定义资源路由部分

3. 非资源路由

除了使用 resources 进行资源路由之外,Rails 还强大支持将任意 URL 路由到操作。您不会自动获得资源路由生成的路由组。相反,您在应用程序中单独设置每个路由。

虽然您通常应该使用资源路由,但在某些地方,非资源路由更合适。如果您的应用程序不适合资源框架,则无需尝试将应用程序的每个部分都强制放入其中。

非资源路由的一个用例是将现有旧版 URL 映射到新的 Rails 动作。

3.1. 绑定参数

当您设置常规路由时,您提供一系列符号,Rails 会将这些符号映射到传入 HTTP 请求的一部分。例如,考虑以下路由

get "photos(/:id)", to: "photos#display"

如果一个传入的 GET 请求 /photos/1 由此路由处理,那么结果将是调用 PhotosControllerdisplay 动作,并将最终参数 "1" 作为 params[:id] 提供。此路由还将把传入的 /photos 请求路由到 PhotosController#display,因为 :id 是一个可选参数,在上面的示例中用括号表示。

3.2. 动态段

您可以在常规路由中设置任意数量的动态段。任何段都将作为 params 的一部分提供给动作。如果您设置此路由

get "photos/:id/:user_id", to: "photos#show"

此路由将响应诸如 /photos/1/2 等路径。params 哈希将是 { controller: 'photos', action: 'show', id: '1', user_id: '2' }。

默认情况下,动态段不接受点 - 这是因为点用作格式化路由的分隔符。如果您需要在动态段中使用点,请添加一个覆盖此规则的约束 - 例如,id: /[^\/]+/ 允许除斜杠之外的任何内容。

3.3. 静态段

您可以通过不给段添加冒号前缀来在创建路由时指定静态段

get "photos/:id/with_user/:user_id", to: "photos#show"

此路由将响应诸如 /photos/1/with_user/2 等路径。在这种情况下,params 将为 { controller: 'photos', action: 'show', id: '1', user_id: '2' }

3.4. 查询字符串

params 也将包含查询字符串中的任何参数。例如,使用此路由

get "photos/:id", to: "photos#show"

/photos/1?user_id=2 的传入 GET 请求将照常分派到 PhotosController 类的 show 动作,并且 params 哈希将为 { controller: 'photos', action: 'show', id: '1', user_id: '2' }

3.5. 定义默认参数

您可以通过为 :defaults 选项提供一个哈希来定义路由中的默认值。这甚至适用于您未指定为动态段的参数。例如

get "photos/:id", to: "photos#show", defaults: { format: "jpg" }

Rails 会将 photos/12 匹配到 PhotosControllershow 动作,并将 params[:format] 设置为 "jpg"

您还可以使用 defaults 块来定义多个项目的默认值

defaults format: :json do
  resources :photos
  resources :articles
end

你不能通过查询参数覆盖默认值——这是出于安全原因。唯一可以被覆盖的默认值是 URL 路径中通过替换实现的动态段。

3.6. 命名路由

您可以使用 :as 选项为任何路由指定一个名称,该名称将由 _path_url 助手使用

get "exit", to: "sessions#destroy", as: :logout

这将在您的应用程序中创建 logout_pathlogout_url 作为路由助手。调用 logout_path 将返回 /exit

您还可以使用 as 通过在资源定义之**前**放置自定义路由定义来覆盖 resources 定义的路由助手名称,如下所示

get ":username", to: "users#show", as: :user
resources :users

这将定义一个 user_path 助手,它将匹配 /:username(例如 /jane)。在 UsersControllershow 动作中,params[:username] 将包含用户的用户名。

3.7. HTTP 动词约束

通常,您应该使用 getpostputpatchdelete 方法将路由限制为特定动词。您可以使用 match 方法与 :via 选项一起匹配多个动词

match "photos", to: "photos#show", via: [:get, :post]

上述路由将 GET 和 POST 请求匹配到 PhotosControllershow 动作。

你可以使用 via: :all 将所有动词匹配到特定路由

match "photos", to: "photos#show", via: :all

GETPOST 请求都路由到单个动作具有安全隐患。例如,GET 动作不会检查 CSRF 令牌(因此从 GET 请求写入数据库不是一个好主意。有关更多信息,请参阅安全指南)。通常,除非您有充分的理由,否则请避免将所有动词路由到单个动作。

3.8. 段约束

您可以使用 :constraints 选项来强制动态段的格式

get "photos/:id", to: "photos#show", constraints: { id: /[A-Z]\d{5}/ }

上面的路由定义要求 id 的长度为 5 个字母数字字符。因此,此路由将匹配诸如 /photos/A12345 的路径,但不会匹配 /photos/893。您可以更简洁地表达相同的路由,如下所示:

get "photos/:id", to: "photos#show", id: /[A-Z]\d{5}/

:constraints 选项接受正则表达式(以及任何响应 matches? 方法的对象),但限制是不能使用正则表达式锚点。例如,以下路由将不起作用:

get "/:id", to: "articles#show", constraints: { id: /^\d/ }

然而,请注意您不需要使用锚点,因为所有路由都在开头和结尾锚定。

例如

get "/:id", to: "articles#show", constraints: { id: /\d.+/ }
get "/:username", to: "users#show"

上述路由将允许共享根命名空间,并且

  • 将始终以数字开头的路径(例如 /1-hello-world)路由到 id 值的 articles
  • 将从不以数字开头的路径(例如 /david)路由到 username 值的 users

3.9. 基于请求的约束

您还可以根据返回 StringRequest 对象上的任何方法来约束路由。

您指定基于请求的约束的方式与指定段约束的方式相同。例如:

get "photos", to: "photos#index", constraints: { subdomain: "admin" }

将匹配到 admin 子域的传入请求,路径。

您还可以通过使用 constraints 块来指定约束

constraints subdomain: "admin" do
  resources :photos
end

将匹配诸如 https://admin.example.com/photos 的内容。

请求约束的工作原理是调用 Request 对象上与哈希键同名的方法,然后将返回值与哈希值进行比较。例如:constraints: { subdomain: 'api' } 将按预期匹配 api 子域。但是,使用符号 constraints: { subdomain: :api } 将不会匹配,因为 request.subdomain 返回 'api' 作为字符串。

约束值应与相应的 Request 对象方法返回类型匹配。

format 约束有一个例外,虽然它是 Request 对象上的一个方法,但它也是每个路径上的一个隐式可选参数。段约束优先,并且 format 约束仅在通过哈希强制执行时才适用。例如,get 'foo', constraints: { format: 'json' } 将匹配 GET /foo,因为格式默认是可选的。

您可以使用 lambda,例如 get 'foo', constraints: lambda { |req| req.format == :json },以便仅将路由与显式 JSON 请求匹配。

3.10. 高级约束

如果您有更高级的约束,可以提供一个响应 matches? 的对象供 Rails 使用。例如,您可能希望将所有受限制列表中的用户路由到 RestrictedListController。您可以这样做:

class RestrictedListConstraint
  def initialize
    @ips = RestrictedList.retrieve_ips
  end

  def matches?(request)
    @ips.include?(request.remote_ip)
  end
end

Rails.application.routes.draw do
  get "*path", to: "restricted_list#index",
    constraints: RestrictedListConstraint.new
end

您还可以将约束指定为 lambda

Rails.application.routes.draw do
  get "*path", to: "restricted_list#index",
    constraints: lambda { |request| RestrictedList.retrieve_ips.include?(request.remote_ip) }
end

matches? 方法和 lambda 都将 request 对象作为参数。

3.10.1. 块形式的约束

您可以以块的形式指定约束。这对于需要将相同规则应用于多个路由的情况很有用。例如

class RestrictedListConstraint
  # ...Same as the example above
end

Rails.application.routes.draw do
  constraints(RestrictedListConstraint.new) do
    get "*path", to: "restricted_list#index"
    get "*other-path", to: "other_restricted_list#index"
  end
end

您也可以使用 lambda

Rails.application.routes.draw do
  constraints(lambda { |request| RestrictedList.retrieve_ips.include?(request.remote_ip) }) do
    get "*path", to: "restricted_list#index"
    get "*other-path", to: "other_restricted_list#index"
  end
end

3.11. 通配符段

路由定义可以包含通配符段,即以星号为前缀的段,例如 *other

get "photos/*other", to: "photos#unknown"

通配符段允许进行“路由通配”,这是一种指定特定参数(上文的 *other)与路由剩余部分匹配的方式。

因此,上面的路由将匹配 photos/12/photos/long/path/to/12,并将 params[:other] 设置为 "12""long/path/to/12"

通配符段可以出现在路由中的任何位置。例如

get "books/*section/:title", to: "books#show"

将匹配 books/some/section/last-words-a-memoir,其中 params[:section] 等于 'some/section'params[:title] 等于 'last-words-a-memoir'

从技术上讲,一个路由甚至可以有多个通配符段。匹配器按照它们出现的顺序将段分配给参数。例如

get "*a/foo/*b", to: "test#index"

将匹配 zoo/woo/foo/bar/baz,其中 params[:a] 等于 'zoo/woo'params[:b] 等于 'bar/baz'

3.12. 格式段

给定此路由定义

get "*pages", to: "pages#show"

通过请求 '/foo/bar.json',您的 params[:pages] 将等于 'foo/bar',并且请求格式为 JSON,在 params[:format] 中。

format 的默认行为是,如果包含在 URL 中,Rails 会自动从 URL 捕获它并将其包含在 params[:format] 中,但 URL 中不需要 format

如果你想匹配不带明确格式的 URL,并忽略包含格式扩展名的 URL,你可以提供 format: false,如下所示

get "*pages", to: "pages#show", format: false

如果你想让格式段强制性,不能省略,你可以提供 format: true,如下所示:

get "*pages", to: "pages#show", format: true

3.13. 重定向

您可以使用路由器中的 redirect 助手将任何路径重定向到任何其他路径

get "/stories", to: redirect("/articles")

您还可以将匹配中的动态段重用于要重定向到的路径

get "/stories/:name", to: redirect("/articles/%{name}")

您还可以为 redirect 提供一个块,该块接收符号化的路径参数和请求对象

get "/stories/:name", to: redirect { |path_params, req| "/articles/#{path_params[:name].pluralize}" }
get "/stories", to: redirect { |path_params, req| "/articles/#{req.subdomain}" }

请注意,默认重定向是 301“永久移动”重定向。请记住,某些网络浏览器或代理服务器会缓存这种类型的重定向,从而导致旧页面无法访问。您可以使用 :status 选项更改响应状态

get "/stories/:name", to: redirect("/articles/%{name}", status: 302)

在所有这些情况下,如果您不提供主机(http://www.example.com),Rails 将从当前请求中获取这些详细信息。

3.14. 路由到 Rack 应用程序

除了将 :to 指定为字符串,例如 'articles#index'(对应于 ArticlesController 类中的 index 方法)之外,您还可以将任何 Rack 应用程序指定为匹配器的端点。

match "/application.js", to: MyRackApp, via: :all

只要 MyRackApp 响应 call 并返回 [status, headers, body],路由器就不会区分 Rack 应用程序和控制器动作。这是 via: :all 的一个合适用途,因为您会希望允许您的 Rack 应用程序处理所有动词。

一个有趣的细节是 - 'articles#index' 会扩展为 ArticlesController.action(:index),它返回一个有效的 Rack 应用程序。

由于 proc/lambda 是响应 call 的对象,您可以内联实现非常简单的路由(例如用于健康检查),例如:get '/health', to: ->(env) { [204, {}, ['']] }

如果您将 Rack 应用程序指定为匹配器的端点,请记住该路由在接收应用程序中将保持不变。使用以下路由,您的 Rack 应用程序应该期望路由为 /admin

match "/admin", to: AdminApp, via: :all

如果您希望您的 Rack 应用程序在根路径而不是,则使用 mount

mount AdminApp, at: "/admin"

3.15. 使用 root

您可以使用 root 方法指定 Rails 应该将 '/' 路由到哪里

root to: "pages#main"
root "pages#main" # shortcut for the above

您通常将 root 路由放在文件的顶部,以便它可以最先匹配。

默认情况下,root 路由主要处理 GET 请求。但也可以将其配置为处理其他动词(例如 root "posts#index", via: :post)。

您也可以在命名空间和作用域内使用 root

root to: "home#index"

namespace :admin do
  root to: "admin#index"
end

以上将匹配 /adminAdminControllerindex 动作,并将 / 匹配到 HomeControllerindex 动作。

3.16. Unicode 字符路由

您可以直接指定 Unicode 字符路由。例如

get "こんにちは", to: "welcome#index"

3.17. 直接路由

您可以通过调用 direct 来创建自定义 URL 助手。例如

direct :homepage do
  "https://rubyonrails.cn"
end

# >> homepage_url
# => "https://rubyonrails.cn"

块的返回值必须是 url_for 方法的有效参数。因此,您可以传递一个有效的字符串 URL、哈希、数组、Active Model 实例或 Active Model 类。

direct :commentable do |model|
  [ model, anchor: model.dom_id ]
end
direct :main do
  { controller: "pages", action: "index", subdomain: "www" }
end

# >> main_url
# => "http://www.example.com/pages"

3.18. 使用 resolve

resolve 方法允许自定义模型的Polymorphic映射。例如

resource :basket

resolve("Basket") { [:basket] }
<%= form_with model: @basket do |form| %>
  <!-- basket form -->
<% end %>

这将生成单数 URL /basket,而不是通常的 /baskets/:id

4. 自定义资源路由

虽然 resources 生成的默认路由和助手通常能很好地为您服务,但您可能需要以某种方式对其进行自定义。Rails 允许通过几种不同的方式自定义资源路由和助手。本节将详细介绍可用的选项。

4.1. 指定要使用的控制器

:controller 选项允许您明确指定要用于资源的控制器。例如

resources :photos, controller: "images"

将识别以 /photos 开头的传入路径,但路由到 Images 控制器

HTTP 动词 路径 Controller#Action 命名路由助手
GET /photos images#index photos_path
GET /photos/new images#new new_photo_path
POST /photos images#create photos_path
GET /photos/:id images#show photo_path(:id)
GET /photos/:id/edit images#edit edit_photo_path(:id)
PATCH/PUT /photos/:id images#update photo_path(:id)
DELETE /photos/:id images#destroy photo_path(:id)

对于命名空间控制器,您可以使用目录表示法。例如

resources :user_permissions, controller: "admin/user_permissions"

这将路由到 Admin::UserPermissionsController 实例。

仅支持目录表示法。不支持使用 Ruby 常量表示法指定控制器(例如 controller: 'Admin::UserPermissions')。

4.2. 指定 id 的约束

您可以使用 :constraints 选项来指定隐式 id 的所需格式。例如

resources :photos, constraints: { id: /[A-Z][A-Z][0-9]+/ }

此声明将 :id 参数限制为匹配给定的正则表达式。路由器将不再将 /photos/1 匹配到此路由。相反,/photos/RR27 将匹配。

您可以使用块形式指定单个约束以应用于多个路由

constraints(id: /[A-Z][A-Z][0-9]+/) do
  resources :photos
  resources :accounts
end

您也可以在此上下文中使用非资源路由部分中提供的更高级约束

默认情况下,:id 参数不接受点 - 这是因为点用作格式化路由的分隔符。如果你需要在 :id 中使用点,请添加一个覆盖此规则的约束 - 例如 id: /[^\/]+/ 允许除斜杠之外的任何内容。

4.3. 覆盖命名路由助手

:as 选项允许您覆盖路由助手的默认命名。例如

resources :photos, as: "images"

这将匹配 /photos 并照常将请求路由到 PhotosController,但会使用 :as 选项的值来命名助手为 images_path 等,如所示:

HTTP 动词 路径 Controller#Action 命名路由助手
GET /photos photos#index images_path
GET /photos/new photos#new new_image_path
POST /photos photos#create images_path
GET /photos/:id photos#show image_path(:id)
GET /photos/:id/edit photos#edit edit_image_path(:id)
PATCH/PUT /photos/:id photos#update image_path(:id)
DELETE /photos/:id photos#destroy image_path(:id)

4.4. 重命名 newedit 路径名

:path_names 选项允许您覆盖路径中默认的 newedit 段。例如

resources :photos, path_names: { new: "make", edit: "change" }

这将允许诸如 /photos/make/photos/1/change 之类的路径,而不是 /photos/new/photos/1/edit

路由助手和控制器操作名称不受此选项更改。所示的两个路径将具有 new_photo_pathedit_photo_path 助手,并且仍会路由到 newedit 操作。

也可以使用 scope 块统一更改所有路由的此选项

scope path_names: { new: "make" } do
  # rest of your routes
end

4.5. 使用 :as 为命名路由助手添加前缀

您可以使用 :as 选项为 Rails 为路由生成的命名路由助手添加前缀。使用此选项可防止使用路径作用域的路由之间发生名称冲突。例如

scope "admin" do
  resources :photos, as: "admin_photos"
end

resources :photos

这将把 /admin/photos 的路由助手从 photos_pathnew_photos_path 等更改为 admin_photos_pathnew_admin_photo_path 等。如果没有在作用域的 resources :photos 上添加 as: 'admin_photos',非作用域的 resources :photos 将没有任何路由助手。

要为一组路由助手添加前缀,请将 :asscope 一起使用

scope "admin", as: "admin" do
  resources :photos, :accounts
end

resources :photos, :accounts

和以前一样,这将把 /admin 作用域资源的助手更改为 admin_photos_pathadmin_accounts_path,并允许非作用域资源使用 photos_pathaccounts_path

namespace 作用域会自动添加 :as 以及 :module:path 前缀。

4.6. 在嵌套资源中使用 :as

:as 选项也可以覆盖嵌套路由中资源的路由助手名称。例如

resources :magazines do
  resources :ads, as: "periodical_ads"
end

这将创建诸如 magazine_periodical_ads_urledit_magazine_periodical_ad_path 之类的路由助手,而不是默认的 magazine_ads_urledit_magazine_ad_path

4.7. 参数化作用域

您可以为路由添加命名参数前缀

scope ":account_id", as: "account", constraints: { account_id: /\d+/ } do
  resources :articles
end

这将为您提供诸如 /1/articles/9 之类的路径,并允许您在控制器、助手和视图中将路径的 account_id 部分作为 params[:account_id] 引用。

它还会生成以 account_ 为前缀的路径和 URL 助手,您可以按预期将对象传递给它们

account_article_path(@account, @article) # => /1/article/9
url_for([@account, @article])            # => /1/article/9
form_with(model: [@account, @article])   # => <form action="/1/article/9" ...>

:as 选项也不是强制性的,但如果没有它,Rails 在评估 url_for([@account, @article]) 或其他依赖 url_for 的助手(例如 form_with)时会引发错误。

4.8. 限制创建的路由

默认情况下,使用 resources 会为七个默认动作(indexshownewcreateeditupdatedestroy)创建路由。您可以使用 :only:except 选项来限制创建哪些路由。

:only 选项告诉 Rails 只创建指定的路由

resources :photos, only: [:index, :show]

现在,对 /photos/photos/:idGET 请求将成功,但对 /photosPOST 请求将无法匹配。

:except 选项指定 Rails 不应创建的路由或路由列表

resources :photos, except: :destroy

在这种情况下,Rails 将创建所有正常路由,除了 destroy 的路由(对 /photos/:idDELETE 请求)。

如果您的应用程序有许多 RESTful 路由,使用 :only:except 来仅生成您实际需要的路由,可以减少内存使用并加快路由过程,方法是消除未使用的路由

4.9. 翻译路径

使用 scope,我们可以更改 resources 生成的路径名称

scope(path_names: { new: "neu", edit: "bearbeiten" }) do
  resources :categories, path: "kategorien"
end

Rails 现在创建到 CategoriesController 的路由。

HTTP 动词 路径 Controller#Action 命名路由助手
GET /kategorien categories#index categories_path
GET /kategorien/neu categories#new new_category_path
POST /kategorien categories#create categories_path
GET /kategorien/:id categories#show category_path(:id)
GET /kategorien/:id/bearbeiten categories#edit edit_category_path(:id)
PATCH/PUT /kategorien/:id categories#update category_path(:id)
DELETE /kategorien/:id categories#destroy category_path(:id)

4.10. 指定资源的单数形式

如果您需要覆盖资源的单数形式,您可以通过 inflections 将规则添加到 Active Support Inflector

ActiveSupport::Inflector.inflections do |inflect|
  inflect.irregular "tooth", "teeth"
end

4.11. 重命名默认路由参数 id

可以使用 :param 选项重命名默认参数名称 id。例如

resources :videos, param: :identifier

现在将使用 params[:identifier] 而不是 params[:id]

    videos GET  /videos(.:format)                  videos#index
           POST /videos(.:format)                  videos#create
 new_video GET  /videos/new(.:format)              videos#new
edit_video GET  /videos/:identifier/edit(.:format) videos#edit
Video.find_by(id: params[:identifier])

# Instead of
Video.find_by(id: params[:id])

您可以覆盖关联模型的 ActiveRecord::Base#to_param 来构造 URL

class Video < ApplicationRecord
  def to_param
    identifier
  end
end
irb> video = Video.find_by(identifier: "Roman-Holiday")
irb> edit_video_path(video)
=> "/videos/Roman-Holiday/edit"

5. 检查路由

Rails 提供了几种不同的方法来检查和测试您的路由。

5.1. 列出现有路由

要获取应用程序中可用路由的完整列表,请在**开发**环境中访问 https://:3000/rails/info/routes。您也可以在终端中执行 bin/rails routes 命令以获得相同的输出。

这两种方法都将列出您的所有路由,顺序与它们在 config/routes.rb 中出现的顺序相同。对于每个路由,您将看到

  • 路由名称(如果有)
  • 使用的 HTTP 动词(如果路由不响应所有动词)
  • 要匹配的 URL 模式
  • 路由的路由参数

例如,以下是 RESTful 路由的 bin/rails routes 输出的一小部分

    users GET    /users(.:format)          users#index
          POST   /users(.:format)          users#create
 new_user GET    /users/new(.:format)      users#new
edit_user GET    /users/:id/edit(.:format) users#edit

路由名称(例如上文的 new_user)可以被视为派生路由助手的基准。要获取路由助手的名称,请在路由名称后添加后缀 _path_url(例如 new_user_path)。

您还可以使用 --expanded 选项打开扩展表格格式模式。

$ bin/rails routes --expanded

--[ Route 1 ]----------------------------------------------------
Prefix            | users
Verb              | GET
URI               | /users(.:format)
Controller#Action | users#index
--[ Route 2 ]----------------------------------------------------
Prefix            |
Verb              | POST
URI               | /users(.:format)
Controller#Action | users#create
--[ Route 3 ]----------------------------------------------------
Prefix            | new_user
Verb              | GET
URI               | /users/new(.:format)
Controller#Action | users#new
--[ Route 4 ]----------------------------------------------------
Prefix            | edit_user
Verb              | GET
URI               | /users/:id/edit(.:format)
Controller#Action | users#edit

5.2. 搜索路由

您可以使用 grep 选项 -g 搜索您的路由。这将输出任何部分匹配 URL 助手方法名称、HTTP 动词或 URL 路径的路由。

$ bin/rails routes -g new_comment
$ bin/rails routes -g POST
$ bin/rails routes -g admin

如果您只想查看映射到特定控制器的路由,可以使用控制器选项:-c

$ bin/rails routes -c users
$ bin/rails routes -c admin/users
$ bin/rails routes -c Comments
$ bin/rails routes -c Articles::CommentsController

如果你将终端窗口调宽,直到输出行不换行,或者使用 --expanded 选项,bin/rails routes 的输出会更容易阅读。

5.3. 列出未使用的路由

您可以使用 --unused 选项扫描应用程序中的未使用路由。Rails 中的“未使用”路由是指在 config/routes.rb 文件中定义但未被应用程序中任何控制器动作或视图引用的路由。例如

$ bin/rails routes --unused
Found 8 unused routes:

     Prefix Verb   URI Pattern                Controller#Action
     people GET    /people(.:format)          people#index
            POST   /people(.:format)          people#create
 new_person GET    /people/new(.:format)      people#new
edit_person GET    /people/:id/edit(.:format) people#edit
     person GET    /people/:id(.:format)      people#show
            PATCH  /people/:id(.:format)      people#update
            PUT    /people/:id(.:format)      people#update
            DELETE /people/:id(.:format)      people#destroy

5.4. Rails Console 中的路由

您可以在 Rails Console 中使用 Rails.application.routes.url_helpers 访问路由助手。它们也可以通过 app 对象访问。例如

irb> Rails.application.routes.url_helpers.users_path
=> "/users"

irb> user = User.first
=> #<User:0x00007fc1eab81628
irb> app.edit_user_path(user)
=> "/users/1/edit"

6. 测试路由

Rails 提供了三个内置断言,旨在简化路由测试

6.1. assert_generates 断言

assert_generates 断言一组特定选项会生成一个特定路径,并且可以与默认路由或自定义路由一起使用。例如

assert_generates "/photos/1", { controller: "photos", action: "show", id: "1" }
assert_generates "/about", controller: "pages", action: "about"

6.2. assert_recognizes 断言

assert_recognizesassert_generates 的反向。它断言给定路径被识别并将其路由到应用程序中的特定位置。例如

assert_recognizes({ controller: "photos", action: "show", id: "1" }, "/photos/1")

您可以提供 :method 参数来指定 HTTP 动词

assert_recognizes({ controller: "photos", action: "create" }, { path: "photos", method: :post })

6.3. assert_routing 断言

assert_routing 断言会双向检查路由。它结合了 assert_generatesassert_recognizes 的功能。它测试路径生成选项,以及选项生成路径

assert_routing({ path: "photos", method: :post }, { controller: "photos", action: "create" })

7. 使用 draw 拆分大型路由文件

在拥有数千条路由的大型应用程序中,单个 config/routes.rb 文件可能变得笨重且难以阅读。Rails 提供了一种使用 draw 宏将单个 routes.rb 文件拆分为多个小文件的方法。

例如,您可以添加一个 admin.rb 文件,其中包含与管理区域相关的所有路由,另一个 api.rb 文件用于与 API 相关的资源等。

# config/routes.rb

Rails.application.routes.draw do
  get "foo", to: "foo#bar"

  draw(:admin) # Will load another route file located in `config/routes/admin.rb`
end
# config/routes/admin.rb

namespace :admin do
  resources :comments
end

Rails.application.routes.draw 块内调用 draw(:admin) 会尝试加载一个与给定参数同名的路由文件(本例中为 admin.rb)。该文件需要位于 config/routes 目录或任何子目录中(即 config/routes/admin.rbconfig/routes/external/admin.rb)。

您可以在辅助路由文件(例如 admin.rb)中使用正常的路由 DSL,但**不要**将其用 Rails.application.routes.draw 块包围。该块只能在主 config/routes.rb 文件中使用。

除非您确实需要,否则请勿使用此功能。拥有多个路由文件会使在一个地方发现路由变得更加困难。对于大多数应用程序(即使是那些拥有数百条路由的应用程序),开发人员拥有单个路由文件会更容易。Rails 路由 DSL 已经提供了使用 namespacescope 以有组织的方式拆分路由的方法。



回到顶部