1 Rails 路由器的用途
Rails 路由器識別 URL 並將它們分派到 controller 的 action 或 Rack 應用程式。它還可以產生路徑和 URL,避免在 views 中硬編碼字串的需要。
1.1 將 URL 連線到程式碼
當您的 Rails 應用程式收到傳入請求時:
GET /patients/17
它要求路由器將其與 controller action 匹配。如果第一個匹配的路由是:
get '/patients/:id', to: 'patients#show'
請求被分派到 patients
controller 的 show
action 和 params
中的 { id: '17' }
。
Rails 在這裡使用snake_case 作為controller 名稱,如果您有多個詞controller 像MonsterTrucksController
,例如您想使用monster_trucks#show
。
1.2 從程式碼產生路徑和 URL
您還可以產生路徑和 URL。如果上面的路由修改為:
get '/patients/:id', to: 'patients#show', as: 'patient'
並且您的應用程式在 controller 中包含此程式碼:
@patient = Patient.find(params[:id])
這在相應的 view 中:
<%= link_to 'Patient Record', patient_path(@patient) %>
那麼路由器會產生路徑/patients/17
。這減少了 view 的脆弱性並使您的程式碼更易於理解。注意在路由helper中不需要指定id。
1.3 設定 Rails 路由器
應用程式或引擎的路由位於檔案 config/routes.rb
中,通常如下所示:
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 原始檔,您可以使用其所有功能來幫助您定義路由,但要注意變數名稱,因為它們可能與路由器的 DSL 方法發生衝突。
包裝路由定義的 Rails.application.routes.draw do ... end
塊是建立路由器 DSL 範圍所必需的,不得刪除。
2 資源路由:Rails 預設
資源路由允許您快速宣告給定資源豐富的 controller 的所有公共路由。一次呼叫 resources
可以為您的 index
、show
、new
、edit
、create
、update
和 destroy
宣告所有必要的路由。
2.1 網路資源
瀏覽器通過使用特定的 HTTP 方法請求 URL 來從 Rails 請求頁面,例如 GET
、POST
、PATCH
、PUT
和 DELETE
。每個方法都是對資源執行操作的請求。資源路由將多個相關請求對映到單個 controller 中的 actions。
當您的 Rails 應用程式收到傳入請求時:
DELETE /photos/17
它要求路由器將其對映到 controller action。如果第一個匹配的路由是:
resources :photos
Rails 會將該請求傳送到 photos
上的 destroy
action controller 和 params
中的 { id: '17' }
。
2.2 CRUD、動詞和 Actions
在 Rails 中,資源豐富的路由提供了 HTTP 動詞和 URL 之間的對映,以 controller actions。按照慣例,每個動作也對映到一個特定的 CRUD 在資料庫中操作。路由檔案中的單個條目,例如:
resources :photos
在您的應用程式中建立七個不同的路由,都對映到 Photos
controller:
HTTP 動詞 | 路徑 | Controller#Action | 用於 |
---|---|---|---|
獲取 | /照片 | 照片#index | 顯示所有照片的列表 |
獲取 | /照片/新 | 照片#new | 返回用於建立新照片的 HTML 表單 |
釋出 | /照片 | 照片#create | 建立新照片 |
獲取 | /照片/:id | 照片#show | 顯示特定照片 |
獲取 | /照片/:id/編輯 | 照片#編輯 | 返回用於編輯照片的 HTML 表單 |
補丁/放置 | /照片/:id | 照片#更新 | 更新特定照片 |
刪除 | /照片/:id | 照片#destroy | 刪除特定照片 |
因為路由器使用 HTTP 動詞和 URL 來匹配入站請求,所以四個 URL 對映到七個不同的 actions。
Rails 路線按照指定的順序進行匹配,因此如果您的 resources :photos
高於 get 'photos/poll'
,則 show
action 路線的 resources
線路將在 4_ZWZHT 之前進行線路匹配。要解決此問題,請將 get
行 ** 移到 resources
行的**上方,使其首先匹配。
2.3 路徑和 URL Helpers
建立一個資源豐富的路由也會在你的應用程式中暴露一些 helpers 到 controllers。在 resources :photos
的情況下:
-
photos_path
返回/photos
-
new_photo_path
返回/photos/new
-
edit_photo_path(:id)
返回/photos/:id/edit
(例如,edit_photo_path(10)
返回/photos/10/edit
) -
photo_path(:id)
返回/photos/:id
(例如,photo_path(10)
返回/photos/10
)
這些 helpers 中的每一個都有一個對應的 _url
助手(例如 photos_url
),它返回以當前主機、埠和路徑字首為字首的相同路徑。
2.4 同時定義多個資源
如果你需要為多個資源建立路由,你可以通過一次呼叫 resources
來定義它們,從而節省一些輸入:
resources :photos, :books, :videos
這與以下工作完全相同:
resources :photos
resources :books
resources :videos
2.5 單一資源
有時,您有一個資源,客戶端總是在不引用 ID 的情況下查詢該資源。例如,您希望 /profile
始終顯示當前登入使用者的個人資料。在這種情況下,您可以使用單一資源將 /profile
(而不是 /profile/:id
)對映到 show
action:
get 'profile', to: 'users#show'
將 String
傳遞給 to:
將期望使用 controller#action
格式。使用 Symbol
時,應將 to:
選項替換為 action:
。當使用沒有 #
的 String
時,應將 to:
選項替換為 controller:
:
get 'profile', action: :show, controller: 'users'
這條足智多謀的路線:
resource :geocoder
resolve('Geocoder') { [:geocoder] }
在您的應用程式中建立六個不同的路由,都對映到 Geocoders
controller:
HTTP 動詞 | 路徑 | Controller#Action | 用於 |
---|---|---|---|
獲取 | /地理編碼器/新 | 地理編碼器#new | 返回用於建立地理編碼器的 HTML 表單 |
釋出 | /地理編碼器 | 地理編碼器#create | 建立新的地理編碼器 |
獲取 | /地理編碼器 | 地理編碼器#show | 顯示唯一的地理編碼器資源 |
獲取 | /地理編碼器/編輯 | 地理編碼器#edit | 返回用於編輯地理編碼器的 HTML 表單 |
補丁/放置 | /地理編碼器 | 地理編碼器#update | 更新唯一的地理編碼器資源 |
刪除 | /地理編碼器 | 地理編碼器#destroy | 刪除地理編碼器資源 |
因為您可能希望對單數路由 (/account
) 和複數路由 (/accounts/45
) 使用相同的控制器,所以單數資源對映到複數 controllers。因此,例如,resource :photo
和 resources :photos
建立對映到同一控制器 (PhotosController
) 的單數和複數路由。
一個單一的資源豐富的路線產生這些 helpers:
-
new_geocoder_path
返回/geocoder/new
-
edit_geocoder_path
返回/geocoder/edit
-
geocoder_path
返回/geocoder
呼叫 resolve
是將 Geocoder
實例轉換為通過 記錄標識 的路由所必需的。
與多個資源一樣,以 _url
結尾的相同 helpers 還將包括主機、埠和路徑字首。
2.6 Controller 名稱空間和路由
您可能希望在名稱空間下組織 controllers 組。最常見的是,您可以將多個管理 controllers 分組到 Admin::
名稱空間下,並將這些 controllers 放在 app/controllers/admin
目錄下。您可以使用 namespace
塊路由到這樣的組:
namespace :admin do
resources :articles, :comments
end
這將為 articles
和 comments
controller 中的每一個建立許多路由。對於 Admin::ArticlesController
,Rails 將建立:
HTTP 動詞 | 路徑 | Controller#Action | 命名路線Helper |
---|---|---|---|
獲取 | /管理員/文章 | 管理員/文章#index | admin_articles_path |
獲取 | /管理員/文章/新 | 管理員/文章#new | new_admin_article_path |
釋出 | /管理員/文章 | 管理員/文章#create | admin_articles_path |
獲取 | /admin/articles/:id | 管理/文章#show | admin_article_path(:id) |
獲取 | /admin/articles/:id/edit | 管理員/文章#edit | edit_admin_article_path(:id) |
補丁/放置 | /admin/articles/:id | 管理員/文章#update | admin_article_path(:id) |
刪除 | /admin/articles/:id | 管理員/文章#destroy | admin_article_path(:id) |
相反,如果您想將 /articles
(不帶字首 /admin
)路由到 Admin::ArticlesController
,您可以使用 scope
塊指定 module:
scope module: 'admin' do
resources :articles, :comments
end
這也可以用於單個路由:
resources :articles, module: 'admin'
相反,如果您想將 /admin/articles
路由到 ArticlesController
(沒有 Admin::
module 字首),您可以使用 scope
塊指定路徑:
scope '/admin' do
resources :articles, :comments
end
這也可以用於單個路由:
resources :articles, path: '/admin/articles'
在這兩種情況下,命名路由 helpers 保持不變,就像您沒有使用 scope
一樣。在最後一種情況下,以下路徑對映到 ArticlesController
:
HTTP 動詞 | 路徑 | Controller#Action | 命名路線Helper |
---|---|---|---|
獲取 | /管理員/文章 | 文章#index | 文章路徑 |
獲取 | /管理員/文章/新 | 文章#new | new_article_path |
釋出 | /管理員/文章 | 文章#建立 | 文章路徑 |
獲取 | /admin/articles/:id | 文章#show | article_path(:id) |
獲取 | /admin/articles/:id/edit | 文章#edit | edit_article_path(:id) |
補丁/放置 | /admin/articles/:id | 文章#更新 | article_path(:id) |
刪除 | /admin/articles/:id | 文章#destroy | article_path(:id) |
提示:如果您需要在 namespace
塊內使用不同的 controller 名稱空間,您可以指定絕對 controller 路徑,例如:get '/foo', to: '/foo#index'
。
2.7 巢狀資源
擁有邏輯上是其他資源的子資源的資源是很常見的。例如,假設您的應用程式包含這些 models:
class Magazine < ApplicationRecord
has_many :ads
end
class Ad < ApplicationRecord
belongs_to :magazine
end
巢狀路由允許您在路由中捕獲這種關係。在這種情況下,您可以包含此路由宣告:
resources :magazines do
resources :ads
end
除了雜誌的路由,此宣告還將廣告路由到 AdsController
。廣告 URL 需要一本雜誌:
HTTP 動詞 | 路徑 | Controller#Action | 用於 |
---|---|---|---|
獲取 | /magazines/:magazine_id/ads | 廣告#index | 顯示特定雜誌的所有廣告列表 |
獲取 | /magazines/:magazine_id/ads/new | 廣告#new | 返回用於建立屬於特定雜誌的新廣告的 HTML 表單 |
釋出 | /magazines/:magazine_id/ads | 廣告#建立 | 建立屬於特定雜誌的新廣告 |
獲取 | /magazines/:magazine_id/ads/:id | 廣告#show | 顯示屬於特定雜誌的特定廣告 |
獲取 | /magazines/:magazine_id/ads/:id/edit | 廣告#edit | 返回用於編輯屬於特定雜誌的廣告的 HTML 表單 |
補丁/放置 | /magazines/:magazine_id/ads/:id | 廣告#更新 | 更新屬於特定雜誌的特定廣告 |
刪除 | /magazines/:magazine_id/ads/:id | 廣告#destroy | 刪除屬於特定雜誌的特定廣告 |
這也將建立路由 helpers,例如 magazine_ads_url
和 edit_magazine_ad_path
。這些 helpers 以 Magazine 的實例作為第一個引數 (magazine_ads_url(@magazine)
)。
2.7.1 巢狀限制
如果願意,您可以在其他巢狀資源中巢狀資源。例如:
resources :publishers do
resources :magazines do
resources :photos
end
end
深度巢狀的資源很快就會變得笨重。例如,在這種情況下,應用程式將識別路徑,例如:
/publishers/1/magazines/2/photos/3
對應的路徑 helper 將是 publisher_magazine_photo_url
,要求您指定所有三個級別的物件。事實上,這種情況已經足夠令人困惑了,以至於 Jamis Buck 的一篇流行的 文章 提出了一個好的 Rails 設計的經驗法則:
提示:資源的巢狀深度不應超過 1 級。
2.7.2 淺巢狀
避免深度巢狀的一種方法(如上所述)是產生作用域在父級之下的集合 actions,以便了解層次結構,但不要巢狀成員 actions。換句話說,只構建具有最少資訊量的路由來唯一標識資源,如下所示:
resources :articles do
resources :comments, only: [:index, :new, :create]
end
resources :comments, only: [:show, :edit, :update, :destroy]
這個想法在描述性路線和深度巢狀之間取得了平衡。存在通過 :shallow
選項實現這一點的速記語法:
resources :articles do
resources :comments, shallow: true
end
這將產生與第一個示例完全相同的路由。您還可以在父資源中指定 :shallow
選項,在這種情況下,所有巢狀資源都將是淺層的:
resources :articles, shallow: true do
resources :comments
resources :quotes
resources :drafts
end
此處的文章資源將為它產生以下路由:
HTTP 動詞 | 路徑 | Controller#Action | 命名路線Helper |
---|---|---|---|
獲取 | /articles/:article_id/comments(.:format) | 評論#index | article_comments_path |
釋出 | /articles/:article_id/comments(.:format) | 評論#create | article_comments_path |
獲取 | /articles/:article_id/comments/new(.:format) | 評論#new | new_article_comment_path |
獲取 | /comments/:id/edit(.:format) | 評論#edit | edit_comment_path |
獲取 | /comments/:id(.:format) | 評論#show | 評論路徑 |
補丁/放置 | /comments/:id(.:format) | 評論#update | 評論路徑 |
刪除 | /comments/:id(.:format) | 評論#destroy | 評論路徑 |
獲取 | /articles/:article_id/quotes(.:format) | 報價#index | article_quotes_path |
釋出 | /articles/:article_id/quotes(.:format) | 報價#create | article_quotes_path |
獲取 | /articles/:article_id/quotes/new(.:format) | 報價#new | new_article_quote_path |
獲取 | /quotes/:id/edit(.:format) | 報價#edit | edit_quote_path |
獲取 | /quotes/:id(.:format) | 報價#show | 報價路徑 |
補丁/放置 | /quotes/:id(.:format) | 報價#update | 報價路徑 |
刪除 | /quotes/:id(.:format) | 報價#destroy | 報價路徑 |
獲取 | /articles/:article_id/drafts(.:format) | 草稿#index | article_drafts_path |
釋出 | /articles/:article_id/drafts(.:format) | 草稿#create | article_drafts_path |
獲取 | /articles/:article_id/drafts/new(.:format) | 草稿#new | new_article_draft_path |
獲取 | /drafts/:id/edit(.:format) | 草稿#edit | edit_draft_path |
獲取 | /drafts/:id(.:format) | 草稿#show | 草稿路徑 |
補丁/放置 | /drafts/:id(.:format) | 草稿#更新 | 草稿路徑 |
刪除 | /drafts/:id(.:format) | 草稿#destroy | 草稿路徑 |
獲取 | /文章(.:格式) | 文章#index | 文章路徑 |
釋出 | /文章(.:格式) | 文章#建立 | 文章路徑 |
獲取 | /articles/new(.:format) | 文章#new | new_article_path |
獲取 | /articles/:id/edit(.:format) | 文章#edit | edit_article_path |
獲取 | /articles/:id(.:format) | 文章#show | article_path |
補丁/放置 | /articles/:id(.:format) | 文章#更新 | article_path |
刪除 | /articles/:id(.:format) | 文章#destroy | article_path |
DSL 的 shallow
方法建立了一個範圍,其中的每個巢狀都是淺層。這將產生與上一個示例相同的路由:
shallow do
resources :articles do
resources :comments
resources :quotes
resources :drafts
end
end
scope
有兩個選項可以自定義淺層路線。 :shallow_path
使用指定引數為成員路徑新增字首:
scope shallow_path: "sekret" do
resources :articles do
resources :comments, shallow: true
end
end
此處的評論資源將為其產生以下路由:
HTTP 動詞 | 路徑 | Controller#Action | 命名路線Helper |
---|---|---|---|
獲取 | /articles/:article_id/comments(.:format) | 評論#index | article_comments_path |
釋出 | /articles/:article_id/comments(.:format) | 評論#create | article_comments_path |
獲取 | /articles/:article_id/comments/new(.:format) | 評論#new | new_article_comment_path |
獲取 | /sekret/comments/:id/edit(.:format) | 評論#edit | edit_comment_path |
獲取 | /sekret/comments/:id(.:format) | 評論#show | 評論路徑 |
補丁/放置 | /sekret/comments/:id(.:format) | 評論#update | 評論路徑 |
刪除 | /sekret/comments/:id(.:format) | 評論#destroy | 評論路徑 |
:shallow_prefix
選項將指定的引數新增到命名路由 helpers 中:
scope shallow_prefix: "sekret" do
resources :articles do
resources :comments, shallow: true
end
end
此處的評論資源將為其產生以下路由:
HTTP 動詞 | 路徑 | Controller#Action | 命名路線Helper |
---|---|---|---|
獲取 | /articles/:article_id/comments(.:format) | 評論#index | article_comments_path |
釋出 | /articles/:article_id/comments(.:format) | 評論#create | article_comments_path |
獲取 | /articles/:article_id/comments/new(.:format) | 評論#new | new_article_comment_path |
獲取 | /comments/:id/edit(.:format) | 評論#edit | edit_sekret_comment_path |
獲取 | /comments/:id(.:format) | 評論#show | sekret_comment_path |
補丁/放置 | /comments/:id(.:format) | 評論#update | sekret_comment_path |
刪除 | /comments/:id(.:format) | 評論#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
您還可以通過呼叫 concerns
在任何地方使用它們。例如,在 scope
或 namespace
塊中:
namespace :articles do
concerns :commentable
end
2.9 從物件建立路徑和 URL
除了使用路由 helpers 之外,Rails 還可以從引數陣列建立路徑和 URL。例如,假設您有這組路由:
resources :magazines do
resources :ads
end
使用 magazine_ad_path
時,您可以傳入 Magazine
和 Ad
的實例而不是數字 ID:
<%= link_to 'Ad details', magazine_ad_path(@magazine, @ad) %>
您還可以將 url_for
與一組物件一起使用,Rails 將自動確定您想要的路線:
<%= link_to 'Ad details', url_for([@magazine, @ad]) %>
在這種情況下,Rails 將看到 @magazine
是一個 Magazine
而 @ad
是一個 Ad
,因此將使用 magazine_ad_path
助手。在類似於 link_to
的 helpers 中,您可以僅指定物件來代替完整的 url_for
呼叫:
<%= link_to 'Ad details', [@magazine, @ad] %>
如果您只想連結到一本雜誌:
<%= link_to 'Magazine details', @magazine %>
對於其他的 actions,你只需要插入動作名稱作為陣列的第一個元素:
<%= link_to 'Edit Ad', [:edit, @magazine, @ad] %>
這允許您將 models 的實例視為 URL,這是使用資源豐富的樣式的 key 優勢。
2.10 新增更多 RESTful Action
您不僅限於 RESTful 路由預設建立的七個路由。如果您願意,可以新增適用於集合或集合的單個成員的其他路由。
2.10.1 新增成員路由
要新增成員路由,只需在資源塊中新增一個 member
塊:
resources :photos do
member do
get 'preview'
end
end
這將使用 GET 識別 /photos/1/preview
,並路由到 PhotosController
的 preview
action,資源 id value 傳入 params[:id]
。它還將建立 preview_photo_url
和 preview_photo_path
helpers。
在成員路由塊中,每個路由名稱都指定了 HTTP 動詞
會被認可。您可以在此處使用 get
、patch
、put
、post
或 delete
.如果你沒有多個 member
路由,你也可以將 :on
傳遞給一個
路線,消除塊:
resources :photos do
get 'preview', on: :member
end
您可以省略 :on
選項,這將建立相同的成員路由,但資源 ID value 將在 params[:photo_id]
中可用,而不是 params[:id]
。路線 helpers 也將從 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 能夠使用 GET 識別路徑,例如 /photos/search
,並路由到 PhotosController
的 search
action。它還將建立 search_photos_url
和 search_photos_path
路線 helpers。
與成員路由一樣,您可以將 :on
傳遞給路由:
resources :photos do
get 'search', on: :collection
end
如果您使用符號作為第一個位置引數定義附加資源路由,請注意它不等同於使用字串。 Symbols 推斷 controller actions 而字串推斷路徑。
2.10.3 為其他新的 Action 新增路由
要使用 :on
快捷方式新增備用的新 action:
resources :comments do
get 'preview', on: :new
end
這將使 Rails 能夠使用 GET 識別路徑,例如 /comments/new/preview
,並路由到 CommentsController
的 preview
action。它還將建立 preview_new_comment_url
和 preview_new_comment_path
路線 helpers。
提示:如果您發現自己在資源豐富的路線中添加了許多額外的 actions,是時候停下來問問自己是否在掩飾其他資源的存在。
3 無資源路線
除了資源路由之外,Rails 還強大的支援將任意 URL 路由到 actions。在這裡,您不會獲得由資源豐富的路由自動產生的路由組。相反,您可以在應用程式中單獨設定每個路由。
雖然您通常應該使用資源豐富的路由,但仍有許多地方更適合更簡單的路由。如果這不合適,則無需嘗試將應用程式的最後一部分硬塞進資源豐富的框架中。
特別是,簡單的路由使得將舊 URL 對映到新的 Rails actions 變得非常容易。
3.1 繫結引數
當您設定正常路由時,您提供一系列 symbols,Rails 對映到傳入 HTTP 請求的部分。例如,考慮這條路線:
get 'photos(/:id)', to: 'photos#display'
如果一個傳入的 /photos/1
請求被這條路由處理(因為它沒有匹配檔案中任何先前的路由),那麼結果將是呼叫 PhotosController
的 display
action,並使最後一個引數 "1"
作為 ZHTW_WTHZ 可用.該路由還將傳入的 /photos
請求路由到 PhotosController#display
,因為 :id
是一個可選引數,用括號表示。
3.2 動態段
您可以根據需要在正常路線中設定任意數量的動態路段。作為 params
的一部分,任何段都可用於 action。如果您設定此路線:
get 'photos/:id/:user_id', to: 'photos#show'
/photos/1/2
的傳入路徑將被排程到 PhotosController
的 show
action。 params[:id]
將是 "1"
,而 params[: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
的傳入路徑將被排程到 Photos
controller 的 show
action。 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
action 匹配,並將 params[:format]
設定為 "jpg"
。
您還可以使用 defaults
塊來定義多個專案的預設值:
defaults format: :json do
resources :photos
end
您不能通過查詢引數覆蓋預設值 - 這是出於安全原因。唯一可以覆蓋的預設值是通過 URL 路徑中的替換進行的動態段。
3.6 命名路由
您可以使用 :as
選項為任何路由指定名稱:
get 'exit', to: 'sessions#destroy', as: :logout
這將在您的應用程式中建立 logout_path
和 logout_url
作為命名路由 helpers。呼叫 logout_path
將返回 /exit
您還可以通過在定義資源之前放置自定義路由來使用它來覆蓋由資源定義的路由方法,如下所示:
get ':username', to: 'users#show', as: :user
resources :users
這將定義一個 user_path
方法,該方法將在 controllers、helpers 和 views 中可用,它們將轉到諸如 /bob
之類的路線。在 UsersController
的 show
action 中,params[:username]
將包含使用者的使用者名稱。如果您不希望引數名稱為 :username
,請更改路由定義中的 :username
。
3.7 HTTP 動詞約束
通常,您應該使用 get
、post
、put
、patch
和 delete
方法將路由限制為特定動詞。您可以使用帶有 :via
選項的 match
方法一次匹配多個動詞:
match 'photos', to: 'photos#show', via: [:get, :post]
您可以使用 via: :all
將所有動詞匹配到特定路由:
match 'photos', to: 'photos#show', via: :all
將 GET
和 POST
請求路由到單個 action 具有安全隱患。通常,除非有充分的理由,否則應避免將所有動詞路由到 action。
Rails 中的 GET
不會檢查 CSRF token。您永遠不應該從 GET
請求寫入資料庫,有關更多資訊,請參閱有關 CSRF 對策的 安全指南。
3.8 段約束
您可以使用 :constraints
選項來強制動態段的格式:
get 'photos/:id', to: 'photos#show', constraints: { id: /[A-Z]\d{5}/ }
此路由將匹配諸如 /photos/A12345
之類的路徑,但不會匹配 /photos/893
。您可以通過這種方式更簡潔地表達相同的路線:
get 'photos/:id', to: 'photos#show', id: /[A-Z]\d{5}/
:constraints
採用正則表示式,但不能使用正則表示式錨點。例如,以下路由將不起作用:
get '/:id', to: 'articles#show', constraints: { id: /^\d/ }
但是,請注意,您不需要使用錨點,因為所有路由都錨定在起點和終點。
例如,以下路由將允許 articles
和 to_param
values 像 1-hello-world
一樣總是以數字開頭,而 users
和 to_param
values 像 david
一樣從不共享根以名稱空間開頭的數字:
get '/:id', to: 'articles#show', constraints: { id: /\d.+/ }
get '/:username', to: 'users#show'
3.9 根據請求的約束
您還可以根據返回 String
的 Request object 上的任何方法來約束路由。
您可以像指定段約束一樣指定根據請求的約束:
get 'photos', to: 'photos#index', constraints: { subdomain: 'admin' }
您還可以使用 constraints
塊指定約束:
namespace :admin do
constraints subdomain: 'admin' do
resources :photos
end
end
請求約束的工作方式是呼叫 Request object 與雜湊 key 同名的方法,然後將返回值與雜湊值進行比較。因此,約束 values 應該匹配對應的 Request 物件方法返回型別。例如: constraints: { subdomain: 'api' }
將按預期匹配 api
子域。但是,使用 symbol constraints: { subdomain: :api }
不會,因為 request.subdomain
將 'api'
作為字串返回。
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 路由萬用字元和萬用字元段
路由通配是一種指定特定引數應與路由的所有剩餘部分匹配的方法。例如:
get 'photos/*other', to: 'photos#unknown'
此路由將匹配 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'
。
通過請求 '/foo/bar.json'
,你的 params[:pages]
將等於 'foo/bar'
,請求格式為 JSON。如果你想要舊的 3.0.x 行為,你可以像這樣提供 format: false
:
get '*pages', to: 'pages#show', format: false
如果你想讓格式段成為強制性的,所以它不能被省略,你可以像這樣提供 format: true
:
get '*pages', to: 'pages#show', format: true
3.12 重定向
您可以使用路由器中的 redirect
helper 將任何路徑重定向到另一個路徑:
get '/stories', to: redirect('/articles')
您還可以重用路徑中匹配項的動態段以重定向到:
get '/stories/:name', to: redirect('/articles/%{name}')
您還可以向 redirect
提供一個塊,它接收 symbolized 路徑引數和請求物件:
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.13 路由到 Rack 應用程式
您可以指定任何 Rack 應用程式 作為匹配器的端點,而不是像 'articles#index'
這樣的字串,它對應於 ArticlesController
中的 index
action:
match '/application.js', to: MyRackApp, via: :all
只要MyRackApp
回應call
並返回[status, headers, body]
,路由器就不會知道Rack應用程式和action之間的差別。這是對 via: :all
的適當使用,因為您將希望讓 Rack 應用程式處理它認為合適的所有動詞。
對於好奇,'articles#index'
實際上擴充套件到 ArticlesController.action(:index)
,它返回一個有效的 Rack 應用程式。
由於 procs/lambdas 是回應 call
的物件,您可以內聯實現非常簡單的路由(例如用於健康檢查):get '/health', to: ->(env) { [204, {}, ['']] }
如果您指定 Rack 應用程式作為匹配器的端點,請記住
接收應用程式中的路由將保持不變。與以下
路由您的 Rack 應用程式應該期望路由為 /admin
:
match '/admin', to: AdminApp, via: :all
如果您希望讓 Rack 應用程式在根目錄接收請求
代替路徑,使用 mount
:
mount AdminApp, at: '/admin'
3.14 使用 root
您可以使用 root
方法指定 Rails 應該將 '/'
路由到什麼位置:
root to: 'pages#main'
root 'pages#main' # shortcut for the above
您應該將 root
路由放在檔案的頂部,因為它是最受歡迎的路由,應該首先匹配。
root
路由只將 GET
請求路由到 action。
您也可以在名稱空間和範圍內使用 root。例如:
namespace :admin do
root to: "admin#index"
end
root to: "home#index"
3.15 Unicode 字元路由
您可以直接指定 unicode 字元路由。例如:
get 'こんにちは', to: 'welcome#index'
3.16 直達路線
您可以通過呼叫 direct
直接建立自定義 URL helpers。例如:
direct :homepage do
"https://rubyonrails.org"
end
# >> homepage_url
# => "https://rubyonrails.org"
塊的返回 value 必須是 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
3.17 使用 resolve
resolve
方法允許自定義 models 的多型對映。例如:
resource :basket
resolve("Basket") { [:basket] }
<%= form_with model: @basket do |form| %>
<!-- basket form -->
<% end %>
這將產生單一的 URL /basket
而不是通常的 /baskets/:id
。
4 自定義資源豐富的路由
雖然 resources
產生的預設路由和 helpers 通常會很好地為您服務,但您可能希望以某種方式自定義它們。 Rails 允許您自定義資源豐富的 helpers 的幾乎任何通用部分。
4.1 指定要使用的 Controller
:controller
選項允許您明確指定用於資源的 controller。例如:
resources :photos, controller: 'images'
將識別以 /photos
開頭的傳入路徑,但路由到 Images
controller:
HTTP 動詞 | 路徑 | Controller#Action | 命名路線Helper |
---|---|---|---|
獲取 | /照片 | 影象#index | 照片路徑 |
獲取 | /照片/新 | 影象#new | new_photo_path |
釋出 | /照片 | 影象#建立 | 照片路徑 |
獲取 | /照片/:id | 影象#show | photo_path(:id) |
獲取 | /照片/:id/編輯 | 影象#edit | edit_photo_path(:id) |
補丁/放置 | /照片/:id | 影象#更新 | photo_path(:id) |
刪除 | /照片/:id | 影象#destroy | photo_path(:id) |
使用 photos_path
、new_photo_path
等為該資源產生路徑。
對於名稱空間 controllers,您可以使用目錄表示法。例如:
resources :user_permissions, controller: 'admin/user_permissions'
這將路由到 Admin::UserPermissions
controller。
僅支援目錄符號。指定
controller 帶有 Ruby 常量符號(例如 controller: 'Admin::UserPermissions'
)
可能會導致路由問題並導致
一個警告。
4.2 指定約束
您可以使用 :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 覆蓋命名路由 Helpers
:as
選項允許您覆蓋命名路由 helpers 的正常命名。例如:
resources :photos, as: 'images'
將識別以 /photos
開頭的傳入路徑並將請求路由到 PhotosController
,但使用 :as
選項的 value 來命名 helpers。
HTTP 動詞 | 路徑 | Controller#Action | 命名路線Helper |
---|---|---|---|
獲取 | /照片 | 照片#index | 影象路徑 |
獲取 | /照片/新 | 照片#new | new_image_path |
釋出 | /照片 | 照片#create | 影象路徑 |
獲取 | /照片/:id | 照片#show | 影象路徑(:id) |
獲取 | /照片/:id/編輯 | 照片#編輯 | edit_image_path(:id) |
補丁/放置 | /照片/:id | 照片#更新 | 影象路徑(:id) |
刪除 | /照片/:id | 照片#destroy | 影象路徑(:id) |
4.4 覆蓋 new
和 edit
段
:path_names
選項允許您覆蓋路徑中自動產生的 new
和 edit
段:
resources :photos, path_names: { new: 'make', edit: 'change' }
這將導致路由識別路徑,例如:
/photos/make
/photos/1/change
此選項不會更改實際操作名稱。顯示的兩條路徑仍將路由到 new
和 edit
actions。
提示:如果您發現自己想要為所有路線統一更改此選項,您可以使用範圍,如下所示:
scope path_names: { new: 'make' } do
# rest of your routes
end
4.5 命名路由的字首 Helpers
您可以使用 :as
選項為 Rails 為路由產生的命名路由 helpers 加上字首。使用此選項可防止使用路徑範圍的路由之間發生名稱衝突。例如:
scope 'admin' do
resources :photos, as: 'admin_photos'
end
resources :photos
這將提供路由 helpers,例如 admin_photos_path
、new_admin_photo_path
等。
要為一組路由 helpers 新增字首,請使用 :as
和 scope
:
scope 'admin', as: 'admin' do
resources :photos, :accounts
end
resources :photos, :accounts
這將產生諸如 admin_photos_path
和 admin_accounts_path
之類的路由,它們分別對映到 /admin/photos
和 /admin/accounts
。
namespace
範圍將自動新增 :as
以及 :module
和 :path
字首。
您也可以使用命名引數為路由新增字首:
scope ':username' do
resources :articles
end
這將為您提供諸如 /bob/articles/1
之類的 URL,並允許您將路徑的 username
部分引用為 controllers、helpers 和 views 中的 params[:username]
。
4.6 限制建立的路由
預設情況下,Rails 為您的應用程式中的每個路由中的七個預設 actions(index
、show
、new
、create
、edit
、update
和 ZHTfulZW_6)建立路由。您可以使用 :only
和 :except
選項來微調此行為。 :only
選項告訴 Rails 只建立指定的路由:
resources :photos, only: [:index, :show]
現在,對 /photos
的 GET
請求會成功,但對 /photos
的 POST
請求(通常會路由到 create
action)將失敗。
:except
選項指定 Rails 應該不建立的路由或路由列表:
resources :photos, except: :destroy
在這種情況下,Rails 將建立除 destroy
的路由(對 /photos/:id
的 DELETE
請求)之外的所有正常路由。
提示:如果您的應用程式有很多 RESTful 路由,使用 :only
和 :except
僅產生您實際需要的路由可以減少記憶體使用並加快路由過程。
4.7 翻譯路徑
使用 scope
,我們可以更改由 resources
產生的路徑名:
scope(path_names: { new: 'neu', edit: 'bearbeiten' }) do
resources :categories, path: 'kategorien'
end
Rails 現在建立到 CategoriesController
的路由。
HTTP 動詞 | 路徑 | Controller#Action | 命名路線Helper |
---|---|---|---|
獲取 | /類別 | 類別#index | category_path |
獲取 | /kategorien/neu | 類別#new | new_category_path |
釋出 | /類別 | 類別#建立 | category_path |
獲取 | /kategorien/:id | 類別#show | category_path(:id) |
獲取 | /kategorien/:id/bearbeiten | 類別#edit | edit_category_path(:id) |
補丁/放置 | /kategorien/:id | 類別#更新 | category_path(:id) |
刪除 | /kategorien/:id | 類別#destroy | category_path(:id) |
4.8 覆蓋單數形式
如果你想覆蓋一個資源的單數形式,你應該通過 inflections
向變形器新增額外的規則:
ActiveSupport::Inflector.inflections do |inflect|
inflect.irregular 'tooth', 'teeth'
end
4.9 在巢狀資源中使用 :as
:as
選項覆蓋巢狀路由 helpers 中資源的自動產生的名稱。例如:
resources :magazines do
resources :ads, as: 'periodical_ads'
end
這將建立路由 helpers,例如 magazine_periodical_ads_url
和 edit_magazine_periodical_ad_path
。
4.10 覆蓋命名路由引數
:param
選項覆蓋預設資源識別符號 :id
(名稱
動態段 用於產生
路線)。您可以使用 controller 訪問該段
params[<:param>]
。
resources :videos, param: :identifier
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(identifier: params[:identifier])
您可以覆蓋關聯的 model 的 ActiveRecord::Base#to_param
來構造
一個網址:
class Video < ApplicationRecord
def to_param
identifier
end
end
video = Video.find_by(identifier: "Roman-Holiday")
edit_video_path(video) # => "/videos/Roman-Holiday/edit"
5 將非常大的路由檔案分解成多個小檔案:
如果您在具有數千條路由的大型應用程式中工作,則單個 config/routes.rb
檔案可能會變得繁瑣且難以閱讀。
Rails 提供了一種使用 draw
巨集將巨大的單個 routes.rb
檔案分解為多個小檔案的方法。
您可以有一個 admin.rb
路由,其中包含管理區域的所有路由,另一個 api.rb
檔案用於 API 相關資源等。
# 設定/路由.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
# 設定/路由/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,但您不應該像在主 config/routes.rb
檔案中那樣用 Rails.application.routes.draw
塊包圍它。
5.1 不要使用這個功能,除非你真的需要它
擁有多個路由檔案會使可發現性和可理解性變得更加困難。對於大多數應用程式——即使是那些有幾百條路由的應用程式——開發人員擁有一個路由檔案更容易。 Rails 路由 DSL 已經提供了一種使用 namespace
和 scope
以有組織的方式中斷路由的方法。
6 檢查和測試路線
Rails 提供用於檢查和測試您的路線的設施。
6.1 列出現有路線
要獲取應用程式中可用路由的完整列表,請在伺服器在 development 環境中執行時在瀏覽器中訪問 http://localhost: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
您還可以使用 --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
您可以使用 grep 選項搜尋您的路線:-g。這將輸出與 URL helper 方法名稱、HTTP 動詞或 URL 路徑部分匹配的任何路由。
$ bin/rails routes -g new_comment
$ bin/rails routes -g POST
$ bin/rails routes -g admin
如果您只想檢視對映到特定 controller 的路由,可以使用 -c 選項。
$ bin/rails routes -c users
$ bin/rails routes -c admin/users
$ bin/rails routes -c Comments
$ bin/rails routes -c Articles::CommentsController
提示:如果您擴大終端視窗直到輸出行不換行,您會發現 bin/rails routes
的輸出更具可讀性。
6.2 測試路線
路由應該包含在您的測試策略中(就像應用程式的其餘部分一樣)。 Rails 提供了三個內建斷言,的目標在於使測試路由更簡單:
6.2.1 assert_generates
斷言
assert_generates
斷言一組特定的選項產生特定的路徑,並且可以與預設路由或自定義路由一起使用。例如:
assert_generates '/photos/1', { controller: 'photos', action: 'show', id: '1' }
assert_generates '/about', controller: 'pages', action: 'about'
6.2.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.2.3 assert_routing
斷言
assert_routing
斷言以兩種方式檢查路由:它測試路徑產生選項,以及選項產生路徑。因此,它結合了 assert_generates
和 assert_recognizes
的功能:
assert_routing({ path: 'photos', method: :post }, { controller: 'photos', action: 'create' })
回饋
我們鼓勵您幫助提高本指南的品質。
如果您發現任何拼寫錯誤或資訊錯誤,請提供回饋。 要開始回饋,您可以閱讀我們的 回饋 部分。
您還可能會發現不完整的內容或不是最新的內容。 請務必為 main 新增任何遺漏的文件。假設是 非穩定版指南(edge guides) 請先驗證問題是否已經在主分支上解決。 請前往 Ruby on Rails 指南寫作準則 查看寫作風格和慣例。
如果由於某種原因您發現要修復的內容但無法自行修補,請您 提出 issue。
關於 Ruby on Rails 的任何類型的討論歡迎提供任何文件至 rubyonrails-docs 討論區。