跳至主要内容
# method in block
namespace

# verb options
to: <String || Function> # controller#action, redirect
controllers: <String>
as: <String> # prefix
path: <String> # uri
on: <Symbol> # :collection | :member
path_names: <Hash>

# special verb
resources <:table_names>, <:table_names>
resource <:table_name>, <:table_name>
root: <String> # controller#action

# resource options
shallow: <Boolean>
param: <Symbol>
only: [:action, :action]
except: [:action, :action]

routes 的檔案位置在 config/routes.rb

顯示路由

透過網址:http://localhost:3000/rails/info/routes,或在終端機輸入:

bin/rails routes                # 列出所有路由
bin/rails routes -g admin # 搜尋特定路由 (grep)
bin/rails routes -c admin/users # 搜尋路由中特定 controller

目錄

[TOC]

路由設定

基本設定

<verb> <resources>, <options>

Rails.application.routes.draw do
# <verb> <resources> <to: controller#action, as: prefix, path: uri>
get '/posts', to: 'posts#index' # 當網址傳到 /posts 時,使用 PostsController 裡面的 index action
get '/profile', to: :show, controller: 'users'
get '/posts/:id/edit', to: 'pages#edit', as: 'edit_page' # :id 到 C 後會變成 params[:id],把這個路徑設定成 Prefix
get '/members', to: 'posts#edit', path: '/admin/edit_members/members_is' # 透過 path 可以改變 URI,一般是搭配 resources 使用
end

如此我們會得到以下路由:

# 若輸入 /admin/edit_members/members_is 會被帶到 posts#edit ,而原本的 /members 則沒有作用了。
Prefix Verb URI Pattern Controller#Action
posts GET /posts(.:format) posts#index
edit_page GET /posts/:id/edit(.:format) pages#edit
members GET /admin/edit_members/members_is(.:format) posts#edit

resources

  • url_path(String)

options

  • to: 此路由要對應到哪個 controller#actionredirect(Function)
  • as: 改變 rails helper 中使用的 prefix,例如 foo_path, foo_url
  • path: 瀏覽器上顯示的 url(顯式出來的網址)
# redirect 使用
get '/stories', to: redirect('/articles') # 直接代入 url
get '/stories/:name', to: redirect('/articles/%{name}') # url 中可代入參數
get '/stories/:name', to: redirect('/articles/%{name}', status: 302) # 可以代入 status code

params

在路由中使用 /:params_name/ 時,可以在 controller 的 params 取得 hash { id: '17' }

  get "/posts/:id/edit", to: "pages#edit", as: "edit_page"                  # :id 到 Controller 後會變成 params[:id],把這個路徑設定成 Prefix

搭配 path 快速變更路徑

# 搭配 path 快速更改路徑,所有原本 /products/ 的 URI 都會變成 /admin/products/。
Rails.application.routes.draw do
resources :products, path: "/admin/products"
end

重覆設定相同路由時

如果我們不小心設定了兩條相同的 routes 時,會以前者為主,後者會被覆蓋

# 設定了兩條相同的 routes 會以前者為主,後者被覆蓋
Rails.application.routes.draw do
get "/about", to: "pages#about"
get "/about", to: "products#about"
end

使用 resource 快速設定

在 Rails 中我們可以使用 resourcesresource 搭配 only:except: 自動幫我們產生一系列的路由。

💡PATCH 和 PUT 的差別:在 Restful API 中,PATCHPUT 的差別在於,PATCH 目的是用來修改某一 record 中的部分欄位,沒有修改到的欄位值會保留下來;而 PUT 則是用來修改某一 record 中的所有欄位,因此沒有修改的欄位值則會全部被改為 null

複數的 resources

resources :[table_names], :[table_names]

使用複數的 resources 會自動產生 [:index, :show, :new, :create, :edit, :update, :destroy]

# 複數的 resources [:index, :show, :new, :create, :edit, :update, :destroy]
# 通常用在管理員可以檢視、新增、修改、刪除會員資料
Rails.application.routes.draw do
resources :users # 透過 resources 快速產生一系列的 CRUD routes 設定,詳細內容見下方
resources :products, only: [:index, :show] # 只需要顯示資料
resources :products, except: [:new, :create, :edit, :update, :destroy] # 不需要新增、修改、刪除
end

會自動產生如下的 routes:

resources

一次定義多個 resources:

resources :photos, :books, :videos

# 等同於
resources :photos
resources :books
resources :videos

單數的 resource

resource :[table_name], :[table_name]

有些時候我們不需要參照到參數 :id,這時候,可以使用單數的 resource,通常會搭配單數的 table_name 使用

# 單數的 resource  [:show, :new, :create, :edit, :update, :destroy]
# 通常用在使用者可以檢視、新增、修改、刪除單筆資訊
Rails.application.routes.draw do
resource :profile
end

產生的路由配置如下:

resource

槽狀式路由

Routing: Nested Resources @ RailsGuides

單純槽狀式

當某一個 resources 是另一個 resources 的孩子時,例如:

# app/controllers/users_controller.rb
class User < ApplicationRecord
has_many :posts
end

# app/controllers/posts_controller.rb
class Post < ApplicationRecord
belongs_to :uses
end

那麼在路由可以設定成在 resources 中在包一層 resources

# 槽狀式,建立 /users/posts/ 並包含所有功能
Rails.application.routes.draw do
resources :users do # 在 users 網址後又加入 posts,詳細內容見下方
resources :posts
end
end

顯示的 routes 如 /users/:user_id/posts/:id,我們多了 users_posts_pathusers_posts_url 這類路由可用,其中可以帶入參數會是 :user_id,例如 users_posts_path(@user)

img

Resources should never be nested more than 1 level deep.

限定式槽狀

resources 中在包一層 resources,並在外層多一個 resources,但透過 only: 限定特定的方法:

# 限定槽狀式,使用者可以檢視和新增自己所發表的文章
Rails.application.routes.draw do
# 管理者可以檢視、新增、刪除 users 資料
resources :users do
# 使用者可以去檢視、新增 posts
resources :posts, only: [:index, :new, :create]
end
# 管理者可以顯示、編輯、刪除 posts
resources :posts, only: [:show, :edit, :update, :destroy]
end

使用 shallow

Routing: shallow-nesting @ RailsGuides

或者我們可以使用 shallow: true 會產生和上面(限定式槽狀)一樣的結果:

# 使用 shallow: true 可以產生一樣的結果
Rails.application.routes.draw do
resources :users do
resources :posts, shallow: true
end
end

產生的路由如 /users/:user_id/posts, /users/:id, /posts/:id

img

collection 和 member 的使用

如果覺得內建的 Resources 產生的 CRUD 不夠用,我們也可以使用 collectionmember 的方法。

  • 使用 collection 方法做出來的路徑不會帶有參數
  • 使用 member 方式產生的路徑,會帶有 :id 在裡面,這個 :id 會傳到 Controller 裡變成 params 這個變數的一部份。

Routing: Adding Member Routes @ RailsGuides

使用 collection:不需要參數時

假設希望可以檢視所有已經刪除的訂單,與其在網址的最後方使用 query(?) 來篩選:

# 在後面加參數的方式
GET /orders?type=cancelled

比較好的作法是給它一個獨立的路徑:

# 給它一個獨立的路徑
GET /orders/cancelled

雖然第一種做法也可以,但這樣表示必須在原來的 Action 裡多判斷、處理傳進來的 type=cancelled 參數;第二種做法則是另外開一個 Action 專門做這件事。

你可以使用 collection 方法來做出這個效果:

# 目標在 /orders 後多一個 cancelled,產生 /orders/cancelled

# 寫法一:使用 collection + block
Rails.application.routes.draw do
resources :orders do
collection do
get :cancelled
end
end
end
# 寫法二:使用 on: :collection
Rails.application.routes.draw do
resources :orders do
get :cancelled, on: :collection
end
end

產生結果如下:

img

使用 member:需要代入參數時

如果我想要「確認第 2 號訂單」或是「取消第 3 號訂單」,網址可能需要這樣設計:

# 確認 2 號訂單
POST /orders/2/confirm

# 取消 3 號訂單
DELETE /orders/3/cancel

這時可以使用 member 來設定:

# 寫法一:使用 member + block
Rails.application.routes.draw do
resources :orders do
member do
post :confirm
delete :cancel
end
end
end
# 寫法二:使用 on: :member
Rails.application.routes.draw do
resources :orders do
post :confirm, on: :member
delete :cancel, on: :member
end
end

產生結果如下:

img

namespaces 和 scope 的使用

Routing: Controller Namespace and Routing @ RailsGuides

使用 namespace:檔案全部放在某一子資料夾中

雖然可以用 "resources :products, path: "admin/products/" 來改變 URI 路徑,但做後台的時候,會更常使用 namespace 方法來把 resources 包起來,隔出後台專屬的路徑:

Rails.application.routes.draw do
namespace :admin do
resources :products
resources :articles
end
end

如此,原來的路由在前面通通加上了 admin,同時對應的 Controller 也變得不一樣了。會產生下列 routes 設定:

img

搭配 path 使用

為了讓後台網址不要太好猜,通常建議不要直接用 admin 或 backend 這樣的後台網址,透過 path ,我們可以修改網址路徑,但依然可以正確對應到 action:

Rails.application.routes.draw do
namespace :admin, path: "cs19oq1ej30" do
resources :products
end
end

會得到如下的 routes 設定:

img

使用 scope:檔案不一定存在子資料夾中

namespace 和 scope 類似,但有更大的彈性。

透過 namespace 如同上面所述,會隔出一個特定 namespace 的路由,並且把 controllers 和對應的 view 放到名為 namespace 的這個資料夾內。

單純使用 scope

當我們單純使用 scope 時,它會改變全部的 path,但不會改變對應到的 controller(controller 不用放在 Admin 資料夾內):

# config/routes.rb
scope :admin do
resources :users
end
Prefix Verb   URI Pattern                Controller#Action
users GET /admin/users(.:format) users#index
POST /admin/users(.:format) users#create
user GET /admin/users/:id(.:format) users#show
PATCH /admin/users/:id(.:format) users#update
PUT /admin/users/:id(.:format) users#update
DELETE /admin/users/:id(.:format) users#destroy

我們可搭配其他的選項。

module

module 讓我們可以 controller 是否要被放在特定的 module(資料夾)下:

# ./config/routes.rb

# 對應到 Admin::UsersController
scope module: 'admin' do
resources :users
end
# 等同於
resources :users, module: 'admin'
Prefix Verb   URI Pattern          Controller#Action
users GET /users(.:format) admin/users#index
POST /users(.:format) admin/users#create
user GET /users/:id(.:format) admin/users#show
PATCH /users/:id(.:format) admin/users#update
PUT /users/:id(.:format) admin/users#update
DELETE /users/:id(.:format) admin/users#destroy

path

透過 path 可以讓我們設定 URI 在 resource name 的前綴:

# ./config/routes.rb
scope module: 'admin', path: 'fu' do
resources :users
end
Prefix Verb   URI Pattern             Controller#Action
users GET /fu/users(.:format) admin/users#index
POST /fu/users(.:format) admin/users#create
user GET /fu/users/:id(.:format) admin/users#show
PATCH /fu/users/:id(.:format) admin/users#update
PUT /fu/users/:id(.:format) admin/users#update
DELETE /fu/users/:id(.:format) admin/users#destroy

as

使用 as 可以改變在 helper 中使用的名稱:

# ./config/routes.rb
scope module: 'admin', path: 'fu', as: 'cool' do
resources :users
end
Prefix Verb   URI Pattern             Controller#Action
cool_users GET /fu/users(.:format) admin/users#index
POST /fu/users(.:format) admin/users#create
cool_user GET /fu/users/:id(.:format) admin/users#show
PATCH /fu/users/:id(.:format) admin/users#update
PUT /fu/users/:id(.:format) admin/users#update
DELETE /fu/users/:id(.:format) admin/users#destroy

Rails 5 Routes: Scope vs Namespace @ DevBlast

使用路由(link_to)

將參數代入路由中

# routes
/users/:id/edit
/users/:id

在 controller 中定義變數 @user

# ./app/controllers/
@user = User.find(3)

可以直接在 View 透過 link_to 的 path 中代入參數:

# ./app/views/
<%= link_to 'Edit', edit_user_path(@user) %> # /users/3/edit
<%= link_to 'Show', @user %> # 如果是直接連到 user_path,可以省略

最後會得到 /patients/17 的連結。

使用 prefix 搭配 _path 和 _url

我們可以在路由中設定 prefix,其中的 Prefix 可以幫助我們使用 helper method,在 Prefix 後面接上 _path_url 後可以變成「產生相對應的路徑或網址」的 View Helper:

  • _path產生站內相對路徑的連結
# 如果是站內連結,通常會使用 _path 寫法來產生站內的路徑,例如:
products + path = products_path => /products
new_product + path = new_product_path => /products/new
edit_product + path = edit_product_path(2) => /products/2/edit
  • _url產生完整路徑的連結,包括主機網域名稱:
# 如果是使用 _url 則會產生完整的路徑,包括主機網域名稱:
products + url = products_url => http://kaochenlong.com/products
new_product + url = new_product_url => http://kaochenlong.com/products/new
edit_product + url = edit_product_url(2) => http://kaochenlong.com/products/2/edit

在 Controller 中使用 Routes 的 Helper

# Rails.application.routes.url_helpers
Rails.application.routes.url_helpers.posts_path
Rails.application.routes.url_helpers.posts_url(:host => "example.com")

其他設定

首頁網址設定(root)

使用 root 關鍵字:

get "/", to: "welcome#index"     # 一般寫法
root "welcome#index" # Rails 提供更簡便的寫法

轉址設定(redirect)

使用 redirect 關鍵字:

get '/users',  to: redirect('/accounts')          # 站內轉址:把連到 /users 轉到 /accounts
get '/5xruby', to: redirect('https://5xruby.tw') # 站外轉址:把連到 /5xruby 轉到 https://5xruby.tw
get 'tasks/:task_id/videos', to: redirect('/apps/%{app_id}/collects?task_id=%{task_id}')

public 不受 routes 控制

並不是所有的 HTTP Request 都會經過 Route,如果是放在專案的 public 目錄裡的檔案是不用經過 Route 的。例如在裡面放了一個檔名為 hello.html 的檔案,這樣一來只要使用者輸入網址 http://localhost:3000/hello.html ,即使 Route 裡沒有這條路徑,還是可以直接讀取到該頁面的內容。

如果你有翻一下 public 目錄的話,就會發現找不到頁面的 404.html 頁面跟伺服器錯誤的 500.html 頁面都在這裡面喔。

參考資料