更多内容请访问 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: "user#show"

请求将匹配到 UsersController 类的 show 操作,params 哈希中包含 { id: '17' }

to: 选项期望传递字符串时使用 controller#action 格式。或者,您可以传递符号并使用 action: 选项而不是 to:。您也可以传递没有 # 的字符串,在这种情况下,controller: 选项将用于 to:。例如

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

Rails 在指定路由时使用 snake_case 作为控制器名称。例如,如果您有一个名为 UserProfilesController 的控制器,则应将路由指定为 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 生成类似于 /users/17 的相对 URL,而 user_url 在上述示例中生成类似于 https://example.com/users/17 的绝对 URL。

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 网络上的资源

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

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

DELETE /photos/17

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

resources :photos

Rails 将把该请求分派给 PhotosController 上的 destroy 操作,params 中包含 { id: '17' }

2.2 CRUD、动词和操作

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

resources :photos

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

HTTP 动词 路径 控制器#操作 用于
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 可以映射到七个不同的控制器操作。例如,相同的 photos/ 路径在动词为 GET 时匹配 photos#index,而在动词为 POST 时匹配 photos#create

routes.rb 文件中的顺序很重要。Rails 路由按照指定的顺序匹配。例如,如果您在 get 'photos/poll' 上面有一个 resources :photos,那么 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”之前使用的前缀是路由名称,可以通过查看 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 动词 路径 控制器#操作 用于
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

这将为每个 articlescomments 控制器创建一个路由集。对于 Admin::ArticlesController,Rails 将创建

HTTP 动词 路径 控制器#操作 命名路由辅助函数
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 动词 路径 控制器#操作 命名路由辅助函数
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 动词 路径 控制器#操作 用于
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

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

HTTP 动词 路径 控制器#操作 命名路由辅助函数
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 动词 路径 控制器#操作 命名路由辅助函数
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 动词 路径 控制器#操作 命名路由辅助函数
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

您还可以调用 concernsscopenamespace 块中,以获得与上述相同的結果。例如

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"

如果传入的 /photos/1GET 请求由该路由处理,那么结果将是调用 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=2GET 请求将像往常一样调度到 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,路由到 articles,其 id 值为。
  • 从不以数字开头的路由路径,例如 /david,路由到 users,其 username 值为。

3.9 基于请求的约束

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

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

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

将匹配具有指向 admin 子域的路径的传入请求。

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

namespace :admin do
  constraints subdomain: "admin" do
    resources :photos
  end
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 的默认行为是,如果包含 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“永久移动”重定向。请记住,某些 Web 浏览器或代理服务器会缓存此类重定向,从而使旧页面无法访问。您可以使用 :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.net.cn"
end

# >> homepage_url
# => "https://rubyonrails.net.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 方法允许自定义模型的多态映射。例如

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 动词 路径 控制器#操作 命名路由辅助函数
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 动词 路径 控制器#操作 命名路由辅助函数
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 动词 路径 控制器#操作 命名路由辅助函数
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://127.0.0.1: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)可以被认为是派生路由助手的基础。要获取路由助手的名称,请在路由名称(例如 new_user_path)中添加后缀 _path_url

您还可以使用 --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 控制台中的路由

您可以在 Rails 控制台 中使用 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 以有组织的方式划分路由的方法。



返回顶部