v7.1.3.2
更多資訊請參閱 rubyonrails.org: 更多 Ruby on Rails

由外而內的 Rails 路由

本指南涵蓋 Rails 路由的使用者介面功能。

閱讀本指南後,您將了解

1 Rails 路由器的目的

Rails 路由器識別 URL,並將它們分派到控制器的動作或 Rack 應用程式。它也可以產生路徑和 URL,避免在檢視中硬編碼字串。

1.1 將 URL 連接到程式碼

當你的 Rails 應用程式收到一個針對

GET /patients/17

的輸入請求時,它會請路由器將其與控制器動作配對。如果第一個配對路由是

get '/patients/:id', to: 'patients#show'

請求會分派到 patients 控制器的 show 動作,並在 params 中傳遞 { id: '17' }

Rails 在這裡使用 snake_case 作為控制器名稱,如果你有一個多字元的控制器,例如 MonsterTrucksController,則需要使用 monster_trucks#show

1.2 從程式碼產生路徑和 URL

您也可以產生路徑和 URL。如果將上方的路由修改為

get '/patients/:id', to: 'patients#show', as: 'patient'

且您的應用程式在控制器中包含這段程式碼

@patient = Patient.find(params[:id])

以及在對應的檢視中包含這段程式碼

<%= link_to 'Patient Record', patient_path(@patient) %>

那麼路由器將會產生路徑 /patients/17。這會降低檢視的脆弱性,讓您的程式碼更容易理解。請注意,在路由輔助程式中不需要指定 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 預設

資源路由讓您可以快速宣告給定資源控制器的所有常見路由。單一呼叫 resources 可以宣告 indexshowneweditcreateupdatedestroy 動作所需的所有路由。

2.1 網路上的資源

瀏覽器透過使用特定 HTTP 方法(例如 GETPOSTPATCHPUTDELETE)對 URL 發出要求,從 Rails 要求頁面。每個方法都是對資源執行操作的要求。資源路由會將許多相關要求對應到單一控制器中的動作。

當你的 Rails 應用程式收到一個針對

DELETE /photos/17

它要求路由器將其對應到控制器動作。如果第一個相符路由是

resources :photos

Rails 會將該要求傳送給 photos 控制器上的 destroy 動作,其中 params 中的 { id: '17' }

2.2 CRUD、動詞和動作

在 Rails 中,足夠的路由會提供 HTTP 動詞和 URL 與控制器動作之間的對應。根據慣例,每個動作也會對應到資料庫中的特定 CRUD 操作。路由檔案中的單一項目,例如

resources :photos

會在應用程式中建立七個不同的路由,全部對應到 Photos 控制器

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 來比對內部要求,因此四個 URL 會對應到七個不同的動作。

Rails 路由會按照指定的順序進行比對,因此如果在 get 'photos/poll' 上方有一個 resources :photos,則 resources 行的 show 動作路由會在 get 行之前進行比對。若要修正此問題,請將 get 行移到 resources 行的上方,以便優先進行比對。

2.3 路徑和 URL 輔助程式

建立豐富的路由也會對應用程式中的控制器公開許多輔助程式。在 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

這些輔助程式各有一個對應的 _url 輔助程式(例如 photos_url),傳回相同路徑,並加上目前的 host、port 和路徑字首。

若要找出路由的路由輔助程式名稱,請參閱下方的 列出現有路由

2.4 同時定義多個資源

如果您需要為多個資源建立路由,您可以透過使用單一呼叫 resources 來定義所有資源,以節省一些輸入。

resources :photos, :books, :videos

這與下列方式完全相同

resources :photos
resources :books
resources :videos

2.5 單數資源

有時,您有一個資源,客戶總是在不參照 ID 的情況下查詢。例如,您希望 /profile 始終顯示目前已登入使用者的個人資料。在這種情況下,您可以使用單數資源將 /profile(而不是 /profile/:id)對應到 show 動作

get 'profile', to: 'users#show'

傳遞 Stringto: 會期待 controller#action 格式。使用 Symbol 時,應將 to: 選項替換為 action:。使用不含 #String 時,應將 to: 選項替換為 controller:

get 'profile', action: :show, controller: 'users'

這個有用的路由

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

在您的應用程式中建立六個不同的路由,全部對應到 Geocoders 控制器

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 刪除地理編碼器資源

由於您可能想要對單數路由 (/account) 和複數路由 (/accounts/45) 使用同一個控制器,因此單數資源會對應到複數控制器。因此,例如,resource :photoresources :photos 會建立單數和複數路由,這些路由會對應到同一個控制器 (PhotosController)。

單數有用的路由會產生這些輔助函式

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

呼叫 resolve 是必要的,用於透過 記錄識別Geocoder 的執行個體轉換為路由。

與複數資源一樣,以 _url 結尾的相同輔助函式也會包含主機、埠和路徑前置詞。

2.6 控制器命名空間和路由

您可能希望在命名空間下組織控制器群組。最常見的是,您可能會將許多管理控制器群組在 Admin:: 命名空間下,並將這些控制器放置在 app/controllers/admin 目錄下。您可以透過使用 namespace 區塊來路由到此類群組

namespace :admin do
  resources :articles, :comments
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)

如果您希望將 /articles(沒有前置詞 /admin)路由到 Admin::ArticlesController,您可以使用 scope 區塊指定模組

scope module: 'admin' do
  resources :articles, :comments
end

這也可以針對單一路由執行

resources :articles, module: 'admin'

如果您希望將 /admin/articles 路由到 ArticlesController(沒有 Admin:: 模組前置詞),您可以使用 scope 區塊指定路徑

scope '/admin' do
  resources :articles, :comments
end

這也可以針對單一路由執行

resources :articles, path: '/admin/articles'

在這些情況下,命名路由輔助函式與您沒有使用 scope 時相同。在最後一個情況下,下列路徑會對應到 ArticlesController

HTTP 動詞 路徑 控制器#動作 命名路由輔助函式
GET /admin/articles articles#index articles_path
GET /admin/articles/new articles#new new_article_path
POST /admin/articles 文章#建立 articles_path
GET /admin/articles/:id 文章#顯示 article_path(:id)
GET /admin/articles/:id/edit 文章#編輯 edit_article_path(:id)
PATCH/PUT /admin/articles/:id 文章#更新 article_path(:id)
DELETE /admin/articles/:id 文章#刪除 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。廣告網址需要雜誌

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 刪除屬於特定雜誌的特定廣告

這也會建立路由輔助程式,例如 magazine_ads_urledit_magazine_ad_path。這些輔助程式將雜誌實例作為第一個參數 (magazine_ads_url(@magazine))。

2.7.1 巢狀限制

如果您喜歡,可以在其他巢狀資源中巢狀資源。例如

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

深度巢狀資源很快就會變得繁瑣。例如,在此情況下,應用程式會辨識路徑,例如

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

對應的路由輔助程式會是 publisher_magazine_photo_url,它需要你在三個層級指定物件。的確,這種情況令人困惑,以至於 Jamis Buck 的熱門文章 提出了一個 Rails 設計的經驗法則

資源絕不應該巢狀超過 1 層。

2.7.2 淺層巢狀

避免深度巢狀的一種方法(如上所述)是在父項下產生範圍化的集合動作,以了解階層,但不要巢狀成員動作。換句話說,只建立具有最少資訊來唯一識別資源的路由,如下所示

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 動詞 路徑 控制器#動作 命名路由輔助函式
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) 引號#顯示 引號路徑
PATCH/PUT /quotes/:id(.:format) 引號#更新 引號路徑
DELETE /quotes/:id(.:format) 引號#銷毀 引號路徑
GET /文章/:文章_id/草稿(.:格式) 草稿#索引 文章草稿路徑
POST /文章/:文章_id/草稿(.:格式) 草稿#建立 文章草稿路徑
GET /文章/:文章_id/草稿/新增(.:格式) 草稿#新增 新增文章草稿路徑
GET /草稿/:id/編輯(.:格式) 草稿#編輯 編輯草稿路徑
GET /草稿/:id(.:格式) 草稿#顯示 草稿路徑
PATCH/PUT /草稿/:id(.:格式) 草稿#更新 草稿路徑
DELETE /草稿/:id(.:格式) 草稿#銷毀 草稿路徑
GET /文章(.:格式) articles#index articles_path
POST /文章(.:格式) 文章#建立 articles_path
GET /文章/新增(.:格式) articles#new new_article_path
GET /文章/:id/編輯(.:格式) 文章#編輯 編輯文章路徑
GET /文章/:id(.:格式) 文章#顯示 文章路徑
PATCH/PUT /文章/:id(.:格式) 文章#更新 文章路徑
DELETE /文章/:id(.:格式) 文章#刪除 文章路徑

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 動詞 路徑 控制器#動作 命名路由輔助函式
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/留言/:id/編輯(.:格式) comments#edit edit_comment_path
GET /sekret/留言/:id(.:格式) comments#show comment_path
PATCH/PUT /sekret/留言/:id(.:格式) comments#update comment_path
DELETE /sekret/留言/:id(.:格式) comments#destroy comment_path

:shallow_prefix 選項會將指定的參數新增到命名路由輔助程式

scope shallow_prefix: "sekret" do
  resources :articles do
    resources :comments, shallow: true
  end
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 編輯_sekret_留言路徑
GET /comments/:id(.:format) comments#show sekret_留言路徑
PATCH/PUT /comments/:id(.:format) comments#update sekret_留言路徑
DELETE /comments/:id(.:format) comments#destroy sekret_留言路徑

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 在任何地方使用它們。例如,在 scopenamespace 區塊中

namespace :articles do
  concerns :commentable
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) %>

您也可以使用 url_for 和一組物件,Rails 會自動判斷您要的路由

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

在這個情況下,Rails 會看到 @magazineMagazine,而 @adAd,因此會使用 magazine_ad_path 輔助程式。在像 link_to 這樣的輔助程式中,您可以只指定物件,取代完整的 url_for 呼叫

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

如果您只想連結到一本雜誌

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

對於其他動作,您只需要將動作名稱插入陣列的第一個元素

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

這讓您可以將模型的執行個體視為 URL,是使用資源風格的一個主要優點。

2.10 加入更多 RESTful 動作

您不限於 RESTful 路由預設建立的七個路由。如果您喜歡,您可以加入適用於集合或集合中個別成員的其他路由。

2.10.1 加入成員路由

若要加入成員路由,只要在資源區塊中加入一個 member 區塊

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

這會以 GET 識別 /photos/1/preview,並路由到 PhotosControllerpreview 動作,其中資源 ID 值會傳遞到 params[:id]。它也會建立 preview_photo_urlpreview_photo_path 輔助程式。

在成員路由區塊中,每個路由名稱會指定會被識別的 HTTP 動詞。您可以在這裡使用 getpatchputpostdelete。如果您沒有多個 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 路由輔助程式。

如果你發現自己為有資源的路由新增許多額外動作,這時候就該停下來問自己是否在掩飾另一個資源的存在。

3 非資源路由

除了資源路由之外,Rails 還強而有力地支援將任意 URL 路由至動作。在這裡,你不會取得由有資源路由自動產生的路由群組。相反地,你會在應用程式中個別設定每個路由。

雖然你通常應該使用有資源路由,但仍有許多地方更適合使用較簡單的路由。如果你覺得不適合,就不用勉強將應用程式的每個部分都塞進有資源架構中。

特別是,簡單路由讓你可以非常輕鬆地將舊版 URL 對應至新的 Rails 動作。

3.1 繫結參數

當您設定常規路由時,您提供一系列 Rails 對應至 HTTP 要求中各部分的符號。例如,考慮此路由

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

如果透過此路由處理 /photos/1 的 HTTP 要求(因為它未與檔案中任何先前的路由相符),結果將呼叫 PhotosControllerdisplay 動作,並將最終參數 "1" 作為 params[:id] 提供。此路由也會將 /photos 的 HTTP 要求路由至 PhotosController#display,因為 :id 是由括號表示的選用參數。

3.2 動態區段

您可以在常規路由中設定任意數量的動態區段。任何區段都將作為 params 的一部分提供給動作。如果您設定此路由

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

/photos/1/2 的 HTTP 路徑將傳送至 PhotosControllershow 動作。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 控制器中的 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/12PhotosControllershow 動作配對,並將 params[:format] 設定為 "jpg"

你也可以使用 defaults 區塊來定義多個項目的預設值

defaults format: :json do
  resources :photos
end

你無法透過查詢參數覆寫預設值 - 這是出於安全原因。唯一可以覆寫的預設值是透過 URL 路徑中的替換來動態區段。

3.6 命名路由

你可以使用 :as 選項為任何路由指定名稱

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

這會在你的應用程式中建立 logout_pathlogout_url 作為已命名路由輔助程式。呼叫 logout_path 會傳回 /exit

你也可以使用它來覆寫資源定義的路由方法,方法是在定義資源之前放置自訂路由,如下所示

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

這會定義一個 user_path 方法,該方法會在控制器、輔助程式和檢視中使用,並會前往類似 /bob 的路由。在 UsersControllershow 動作中,params[:username] 會包含使用者的使用者名稱。如果你不希望你的參數名稱是 :username,請在路由定義中變更 :username

3.7 HTTP 動詞約束

一般來說,您應該使用 getpostputpatchdelete 方法將路徑限制為特定動詞。您可以使用 match 方法搭配 :via 選項,一次比對多個動詞

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

您可以使用 via: :all 將所有動詞比對到特定路徑

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

GETPOST 要求都導向同一個動作會產生安全性問題。一般來說,除非有充分理由,否則您應該避免將所有動詞都導向同一個動作。

Rails 中的 GET 動作不會檢查 CSRF 令牌。您絕不應該透過 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 會採用正規表示式,但限制是不能使用 regexp 錨點。例如,下列路徑將無法運作

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

不過,請注意您不需要使用錨點,因為所有路徑在起點和終點都會錨定。

例如,下列路由允許 articlesto_param 值為 1-hello-world,總是從數字開始,而 usersto_param 值為 david,從不以數字開始,以共用根命名空間

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

3.9 基於請求的約束

您也可以根據 Request 物件 上任何傳回 String 的方法,來限制路由。

您指定基於請求的約束,方式與指定區段約束相同

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

您也可以使用 constraints 區塊來指定約束

namespace :admin do
  constraints subdomain: 'admin' do
    resources :photos
  end
end

請求約束透過呼叫 Request 物件 上與雜湊金鑰名稱相同的方法,然後將傳回值與雜湊值進行比較,來運作。因此,約束值應與對應的 Request 物件方法傳回類型相符。例如:constraints: { subdomain: 'api' } 會如預期般符合 api 次網域。然而,使用符號 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 進階約束

如果你有更進階的約束,你可以提供一個物件來回應 Rails 應使用的 matches?。假設你想要將受限清單上的所有使用者路由到 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 輔助程式將任何路徑重新導向至另一個路徑

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.13 導向至 Rack 應用程式

與對應於 ArticlesControllerindex 動作的字串 'articles#index' 不同,您可以指定任何 Rack 應用程式 作為比對器的終端點

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

只要 MyRackApp 會回應 call 並傳回 [status, headers, body],路由器就無法分辨 Rack 應用程式和動作之間的差異。這是適當使用 via: :all 的情況,因為您會希望讓您的 Rack 應用程式處理它認為適當的所有動詞。

對於好奇的人來說,'articles#index' 實際上會擴充為 ArticlesController.action(:index),它會傳回一個有效的 Rack 應用程式。

由於程序/lambda 是會回應 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 要求路由到動作。

您也可以在命名空間和範圍內使用 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 輔助程式。例如

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

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

區塊的傳回值必須是 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 方法允許自訂模型的多型映射。例如

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)

使用 photos_pathnew_photo_path 等來產生此資源的路徑。

對於命名空間控制器,您可以使用目錄表示法。例如

resources :user_permissions, controller: 'admin/user_permissions'

這將路由至 Admin::UserPermissions 控制器。

僅支援目錄表示法。使用 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 覆寫命名路由輔助函式

:as 選項讓您可以覆寫命名路由輔助函式的常規命名。例如

resources :photos, as: 'images'

將辨識以 /photos 開頭的輸入路徑,並將請求路由到 PhotosController,但使用 :as 選項的值來命名輔助函式。

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

此選項不會變更實際的動作名稱。顯示的兩個路徑仍會路由到 newedit 動作。

如果您發現自己想要針對所有路由統一變更此選項,您可以使用範圍,如下所示

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

4.5 為命名路由輔助函式加上前置字

您可以使用 :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.5.1 參數範圍

你可以使用命名參數為路由加上前綴

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" ...>

我們使用 約束 將範圍限制為僅符合類似 ID 的字串。你可以根據你的需求變更約束,或完全省略它。:as 選項也不是絕對必要的,但如果沒有它,Rails 會在評估 url_for([@account, @article]) 或其他依賴 url_for 的輔助函式(例如 form_with)時產生錯誤。

4.6 限制建立的路由

預設情況下,Rails 會為應用程式中的每個 RESTful 路由建立七個預設動作(indexshownewcreateeditupdatedestroy)的路由。你可以使用 :only:except 選項微調此行為。:only 選項告訴 Rails 僅建立指定的路由

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

現在,對 /photosGET 要求會成功,但對 /photosPOST 要求(通常會路由到 create 動作)會失敗。

:except 選項指定 Rails 不應建立的路由或路由清單

resources :photos, except: :destroy

在這種情況下,Rails 會建立所有正常的路由,除了 destroy 的路由(對 /photos/:idDELETE 要求)。

如果你的應用程式有許多 RESTful 路由,使用 :only:except 僅產生你實際需要的路由,可以減少記憶體使用量並加快路由程序。

4.7 已翻譯路徑

使用 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.8 取代單數形式

如果您想要取代資源的單數形式,您應該透過 inflections 新增額外的規則至 inflector

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

4.9 在巢狀資源中使用 :as

:as 選項會取代巢狀路由輔助程式中自動產生的資源名稱。例如

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

這將會建立路由輔助程式,例如 magazine_periodical_ads_urledit_magazine_periodical_ad_path

4.10 取代已命名路由參數

:param 選項會取代預設資源識別碼 :id(用於產生路由的 動態區段 名稱)。您可以使用 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])

您可以取代關聯模型的 ActiveRecord::Base#to_param 來建構 URL

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 相關的資源,等等。

# 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,但您不應該像在主 config/routes.rb 檔案中那樣,將其置於 Rails.application.routes.draw 區塊中。

5.1 除非您真的需要,否則請勿使用此功能

擁有多個路由檔案會讓可發現性和可理解性更困難。對於大多數應用程式而言,即使是擁有數百個路由的應用程式,開發人員也比較容易擁有一個單一的路由檔案。Rails 路由 DSL 已經提供了一種使用 namespacescope 以有組織的方式中斷路由的方法。

6 檢查和測試路由

Rails 提供了檢查和測試路由的工具。

6.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

您也可以使用 --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 輔助方法名稱、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

如果您將終端機視窗加寬,直到輸出行不再換行,您會發現從 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_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.2.3 assert_routing 斷言

assert_routing 斷言會雙向檢查路由:它會測試路徑是否會產生選項,以及選項是否會產生路徑。因此,它結合了 assert_generatesassert_recognizes 的功能

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

回饋

我們鼓勵您協助提升本指南的品質。

如果您看到任何錯字或事實錯誤,請協助我們修正。要開始,您可以閱讀我們的 文件貢獻 部分。

您也可能會發現內容不完整或過時。請務必新增 main 遺漏的任何文件。請務必先查看 Edge Guides,以驗證問題是否已在主分支中修正。查看 Ruby on Rails Guides Guidelines 以了解風格和慣例。

如果您發現需要修正的地方,但無法自行修補,請 開啟問題

最後但並非最不重要的一點是,歡迎在 官方 Ruby on Rails 論壇 上討論與 Ruby on Rails 文件相關的任何事項。