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_path 和 user_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,即可声明 index、show、new、edit、create、update 和 destroy 动作的所有必要路由,而无需单独声明每个路由。
2.1. Web 上的资源
浏览器通过使用特定的 HTTP 动词(例如 GET、POST、PATCH、PUT 和 DELETE)请求 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 动词 | 路径 | 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 :photos 在 get '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/newedit_geocoder_path返回/geocoder/editgeocoder_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_url 和 edit_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 来标识它们所作用的特定资源的动作,例如 show、edit 等。“集合”动作是作用于整个资源集的动作,例如 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
您还可以在 scope 或 namespace 块中调用 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 时,您可以传入 Magazine 和 Ad 的实例,而不是数字 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::Naming 和 ActiveModel::Conversion 模块中的方法。具体来说,@magazine.model_name.route_key 返回 magazines,@magazine.to_param 返回模型 id 的字符串表示。因此,对于对象 [@magazine, @ad],生成的路径可能类似于 /magazines/1/ads/42。
2.10. 添加更多 RESTful 路由
您不限于 RESTful 路由默认创建的七个路由。您可以添加适用于集合或集合中单个成员的其他路由。
以下部分描述了添加成员路由和集合路由。术语 member 指的是作用于单个元素的路由,例如 show、update 或 destroy。术语 collection 指的是作用于多个或一组元素的路由,例如 index 路由。
2.10.1. 添加成员路由
您可以像这样在资源块中添加一个 member 块
resources :photos do
member do
get "preview"
end
end
对 /photos/1/preview 的传入 GET 请求将路由到 PhotosController 的 preview 动作。资源 ID 值将在 params[:id] 中可用。它还将创建 preview_photo_url 和 preview_photo_path 助手。
在 member 块中,每个路由定义都指定 HTTP 动词(在上面的示例中,get 'preview' 使用 get)。除了 get,您还可以使用 patch、put、post 或 delete。
如果你没有多个 member 路由,你也可以将 :on 传递给路由,从而省去该块
resources :photos do
get "preview", on: :member
end
你也可以省略 :on 选项,这将创建相同的成员路由,只不过资源 ID 值将分别在 params[:photo_id] 而不是 params[:id] 中可用。路由助手也将从 preview_photo_url 和 preview_photo_path 重命名为 photo_preview_url 和 photo_preview_path。
2.10.2. 添加集合路由
要向集合添加路由,请使用 collection 块:
resources :photos do
collection do
get "search"
end
end
这将使 Rails 能够识别诸如 /photos/search(使用 GET 请求)的路径,并路由到 PhotosController 的 search 动作。它还将创建 search_photos_url 和 search_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 请求)的路径,并路由到 CommentsController 的 preview 动作。它还将创建 preview_new_comment_url 和 preview_new_comment_path 路由助手。
如果您发现自己正在为资源路由添加许多额外的操作,那么是时候停下来问自己是否正在掩盖另一个资源的存在了。
可以自定义 resources 生成的默认路由和助手,更多信息请参阅自定义资源路由部分。
3. 非资源路由
除了使用 resources 进行资源路由之外,Rails 还强大支持将任意 URL 路由到操作。您不会自动获得资源路由生成的路由组。相反,您在应用程序中单独设置每个路由。
虽然您通常应该使用资源路由,但在某些地方,非资源路由更合适。如果您的应用程序不适合资源框架,则无需尝试将应用程序的每个部分都强制放入其中。
非资源路由的一个用例是将现有旧版 URL 映射到新的 Rails 动作。
3.1. 绑定参数
当您设置常规路由时,您提供一系列符号,Rails 会将这些符号映射到传入 HTTP 请求的一部分。例如,考虑以下路由
get "photos(/:id)", to: "photos#display"
如果一个传入的 GET 请求 /photos/1 由此路由处理,那么结果将是调用 PhotosController 的 display 动作,并将最终参数 "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 匹配到 PhotosController 的 show 动作,并将 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_path 和 logout_url 作为路由助手。调用 logout_path 将返回 /exit。
您还可以使用 as 通过在资源定义之**前**放置自定义路由定义来覆盖 resources 定义的路由助手名称,如下所示
get ":username", to: "users#show", as: :user
resources :users
这将定义一个 user_path 助手,它将匹配 /:username(例如 /jane)。在 UsersController 的 show 动作中,params[:username] 将包含用户的用户名。
3.7. HTTP 动词约束
通常,您应该使用 get、post、put、patch 和 delete 方法将路由限制为特定动词。您可以使用 match 方法与 :via 选项一起匹配多个动词
match "photos", to: "photos#show", via: [:get, :post]
上述路由将 GET 和 POST 请求匹配到 PhotosController 的 show 动作。
你可以使用 via: :all 将所有动词匹配到特定路由
match "photos", to: "photos#show", via: :all
将 GET 和 POST 请求都路由到单个动作具有安全隐患。例如,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. 基于请求的约束
您还可以根据返回 String 的 Request 对象上的任何方法来约束路由。
您指定基于请求的约束的方式与指定段约束的方式相同。例如:
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
以上将匹配 /admin 到 AdminController 的 index 动作,并将 / 匹配到 HomeController 的 index 动作。
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. 重命名 new 和 edit 路径名
:path_names 选项允许您覆盖路径中默认的 new 和 edit 段。例如
resources :photos, path_names: { new: "make", edit: "change" }
这将允许诸如 /photos/make 和 /photos/1/change 之类的路径,而不是 /photos/new 和 /photos/1/edit。
路由助手和控制器操作名称不受此选项更改。所示的两个路径将具有 new_photo_path 和 edit_photo_path 助手,并且仍会路由到 new 和 edit 操作。
也可以使用 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_path、new_photos_path 等更改为 admin_photos_path、new_admin_photo_path 等。如果没有在作用域的 resources :photos 上添加 as: 'admin_photos',非作用域的 resources :photos 将没有任何路由助手。
要为一组路由助手添加前缀,请将 :as 与 scope 一起使用
scope "admin", as: "admin" do
resources :photos, :accounts
end
resources :photos, :accounts
和以前一样,这将把 /admin 作用域资源的助手更改为 admin_photos_path 和 admin_accounts_path,并允许非作用域资源使用 photos_path 和 accounts_path。
namespace 作用域会自动添加 :as 以及 :module 和 :path 前缀。
4.6. 在嵌套资源中使用 :as
:as 选项也可以覆盖嵌套路由中资源的路由助手名称。例如
resources :magazines do
resources :ads, as: "periodical_ads"
end
这将创建诸如 magazine_periodical_ads_url 和 edit_magazine_periodical_ad_path 之类的路由助手,而不是默认的 magazine_ads_url 和 edit_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 会为七个默认动作(index、show、new、create、edit、update 和 destroy)创建路由。您可以使用 :only 和 :except 选项来限制创建哪些路由。
:only 选项告诉 Rails 只创建指定的路由
resources :photos, only: [:index, :show]
现在,对 /photos 或 /photos/:id 的 GET 请求将成功,但对 /photos 的 POST 请求将无法匹配。
:except 选项指定 Rails 不应创建的路由或路由列表
resources :photos, except: :destroy
在这种情况下,Rails 将创建所有正常路由,除了 destroy 的路由(对 /photos/:id 的 DELETE 请求)。
如果您的应用程序有许多 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_recognizes 是 assert_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_generates 和 assert_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.rb 或 config/routes/external/admin.rb)。
您可以在辅助路由文件(例如 admin.rb)中使用正常的路由 DSL,但**不要**将其用 Rails.application.routes.draw 块包围。该块只能在主 config/routes.rb 文件中使用。
除非您确实需要,否则请勿使用此功能。拥有多个路由文件会使在一个地方发现路由变得更加困难。对于大多数应用程序(即使是那些拥有数百条路由的应用程序),开发人员拥有单个路由文件会更容易。Rails 路由 DSL 已经提供了使用 namespace 和 scope 以有组织的方式拆分路由的方法。