Link Search Menu Expand Document

路由(Routing)

Weeks of programming can save you hours of planning. – Unknown

不同於靜態網頁的路由是直接對應於檔案的目錄結構,一個Web開發框架會將路由功能納入其中,來獲得最大的彈性。也就是您可以指定任意URL對應到任一個ControllerAction。另一方面,我們也不在Views中直接寫死URL網址,而是透過Helper輔助方法根據你的路由設定來產生URL,這樣也可以確定該網址一定有對應的Controller和Action,不然就會出現NoMethodError找不到Helper方法的錯誤。

也就是,路由系統做幾件事情:

1. 辨識HTTP RequestURL網址,然後對應到設定的Controller Action

2. 處理網址內的參數字串,例如:/users/show/123送到Users controllershow action時,會將params[:id] 設定為 123

3. 辨識link_toredirect_to的參數產生URL字串,例如

link_to 'hola!', { :controller=> 'welcome', :action => 'say' }

會產生

<a href="/welcome/say">hola!</a>

Rails這麼彈性的路由功能,可以怎麼用呢?例如設計一個部落格網站,如果是沒有使用框架的CGIPHP網頁開發,會長得這樣:

http://example.org/?p=123

但是如果我們想要將編號放在網址列中呢?

http://example.org/posts/123

或是希望根據日期:

http://example.org/posts/2011/04/21/

或者是根據不同作者加上文章的標籤(將關鍵字放在網址中有助於SEO):

http://example.org/ihower/posts/123-ruby-on-rails

這些在Rails只需要修改config/routes.rb這一個路由檔案,就可以完全自由自定。讓我們看看有哪些設定方式吧:

一般路徑Regular Routes

get 'meetings/:id', :to => 'events#show'
post 'meetings', :to => 'events#create'

這裡的events#show表示指向events controllershow action。通常會簡寫成:

get 'meetings/:id' => 'events#show'

其中有冒號:id的部分,會被轉成一個參數params[:id]傳進Controller裡。

注意到在routes.rb中,越上面越優先。是如果有網址同時符合多個規則,會使用最上面的規則。

外卡路由

match ':controller(/:action(/:id(.:format)))', :via => :all

這是我們在上一章所使用的方式,也是Rails 3.0之前版本的預設方式。其中的括弧用法表示這部份可有可無,也就是上述這一行設定就包括六種路徑方式:

match '/:controller', via: :all
match '/:controller/:action', via: :all
match '/:controller/:action/:id', via: :all
match '/:controller.:format', via: :all
match '/:controller/:action.:format', via: :all
match '/:controller/:action/:id.:format', via: :all

例如,像這樣的網址http://localhost:3000/welcome/say便會對應到welcome controllersay action。外卡路由是一種非常簡便的對應方式。這種方式的缺點當網站的Action變多的時候,會容易讓Controller的設計變得混亂沒有規則。稍後介紹的RESTful路由則是Rails對此提出的組織路由方案。

還有,(.format)這一段則會讓路由可以接受.json.xml等有副檔名的網址,並且轉成params[:format]參數傳進Controller裡,搭配respond_to而回傳不同的格式。

命名路由Named Routes

Named Routes可以幫助我們產生URL helpermeetings_urlmeetings_path,而不需要用{:controller => 'meetings', :action => 'index'}的方式:

get '/meetings' => 'events#index', :as => "meetings"

其中:as的部份就會產生一個meetings_pathmeetings_urlHelpers_path_url的差別在於前者是相對路徑,後者是絕對路徑。一般來說比較常用_path方法,除非像是在Email信件中,才必須用_url提供包含Domain的完整網址。

雖然RESTful已經是設計Rails最常見的路徑模式,但是在一些特殊的情況、不符合CRUD模型的情結就不一定適用了,例如有多重步驟的表單(又叫作Wizard) 時,使用命名路由反而會比較簡潔,例如step1_path, step2_path, step3_path等。

Redirect

在路由中可以直接設定轉向:

get "/foo" => redirect("/bar")
get "/ihower" => redirect("https://ihower.tw")

設定首頁

要設定網站的首頁,請設定:

root :to => 'welcome#show'

HTTP動詞(Verb)限定

可以透過 :via 參數指定 HTTP Verb 動詞

match "account/overview" => "account#overview", :via => :get
match "account/setup" => "account#setup", :via => [:get, :post]
match "account/overview" => "account#overview", :via => :all

或是

get "account/overview" => "account#overview"
get "account/setup" => "account#setup"
post "account/setup" => "account#setup"

Scope 規則

scope方法可以讓我們DRY我們的路由規則,將共通的controllerconstraints、網址前置pathURL Helper前置名稱移到scope成為參數。例如

get 'foo/meetings/:id', :to => 'events#show'
post 'foo/meetings', :to => 'events#create'

可以改寫成

scope :controller => "events", :path => "/foo", :as => "bar" do
  get 'meetings/:id' => :show, :as => "meeting"
  post 'meetings' => :create	, :as => "meetings"
end

其中as會產生URL helperbar_meeting_urlbar_meetings_url

Scope Module

Module參數則可以讓ControllerModule,例如

scope :path => '/api/v1/', :module => "api_v1", :as => 'v1' do
  resources :projects
end

如此controller會是ApiV1::ProjectsController,網址如/api/v1/projects,而URL Helperv1_projects_path這樣的形式。

領域名稱Namespace

NamespaceScope的一種特定應用,特別適合例如後台介面,這樣就整組controller、網址pathURL Helper前置名稱`都影響到:

namespace :admin do
  resources :projects
end

如此controller會是Admin::ProjectsController,網址如/admin/projects,而URL Helperadmin_projects_path這樣的形式。

Namespace下也可以設定它的首頁,例如:

namespace :admin do
	root "projects#index"
end

就樣連http://localhost:3000/admin/就會使用ProjectsController index action了。

特殊條件限定

我們可以利用:constraints設定一些參數限制,例如限制:id必須是整數。

match "/events/show/:id" => "events#show", :constraints => {:id => /\d/}

另外也可以限定subdomain子網域:

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

甚至可以限定IP位置:

constraints(:ip => /(^127.0.0.1$)|(^192.168.[0-9]{1,3}.[0-9]{1,3}$)/) do
    match "/events/show/:id" => "events#show"
end

RESTful路由

我們在第六章介紹過RESTful路由的來龍去脈,接下來仔細看看其中的設定。

複數資源

resources :events

單數資源Singular Resoruce

除了一般複數型Resources,在單數的使用情境下也可以設定成單數Resource

resource :map

特別之處在於那就沒有index action了,所有的URL Helper也皆為單數形式,顯示出來的網址也是單數。

但是Singular resource的檔案命名仍為複數,例如maps_controller.rb

套疊Nested Resources

當一個Resource一定會依存另一個Resource時,我們可以套疊多層的Resources,例如以下是任務一定屬於在專案底下:

resources :projects do
  resources :tasks
end

如此產生的URL Helperproject_tasks_path(@project)project_task_path(@project, @task),它的網址會如projects/123/tasksprojects/123/tasks/123

實務上不建議設計超過兩層,一來是路由會太長,二來也是不必要的依賴。

指定Controller

resource預設採用同名的controller,我們可以改指定,例如

resources :projects do
  resources :tasks, :controller => "project_tasks"
end

自定群集路由Collection

除了慣例中的七個Actions外,如果你需要自定群集的Action,可以這樣設定:

resources :products do
  collection do
    get  :sold
    post :on_offer
  end

  # 或
  get  :sold, :on => :collection
  post :on_offer, :on => :collection
end

如此便會有sold_products_pathon_offer_products_path這兩個URL Helper,產生出如products/soldproducts/on_offer這樣的網址。

自定特定元素路由Member

如果需要自定對特定元素的Action

resources :products do
  member do
  	get :sold
  end

  # 或
  get :sold, :on => :member
end

如此會有sold_product_path(@product)這個URL Helper,產生出如products/123/sold這樣的網址。

限定部分支援

透過exceptonly參數,我們不一定要啟用預設的七個Resource路由,例如

resources :events, :except => [:index, :show]
resources :events, :only => :create

PATCH v.s. PUT

PATCH是一個相對新的HTTP verbRails為了保持相容性這兩個HTTP verbs都會進到update action之中。而編輯表單預設則是用PATCH。在REST語意上的差別是:

  • PATCH 用於修改部分資料
  • PUT 用來替換資料(replace)

HTTP API設計有興趣的讀者,可以參考https://ihower.tw/blog/archives/6483一文。

rake routes

如果你不清楚這些路由設定到底最後的規則是什麼,你可以執行:

rake routes

這樣就會產生出所有URL HelperURL 網址和對應的Controller Action都列出來。

常見錯誤

Routing Error

URL找不到任何路由規則可以符合時,會出現這個錯誤。例如一個GET的路由,你用button_to送出POST,這樣就不符合規則。

ActionController::UrlGenerationError

當一個路由Helper的參數不夠的時候,會出現這個錯誤。例如event_path(event)這個方法的event參數不能是nil。如果你打錯成event_path(@events)@events是個nil,就會出現這個錯誤。

結論

透過RESTfulNamed Route,我們就不再需要透過外卡路由的Hash來指定路由了。所有的路由規則都可以在routes.rb一目了然。

線上參考資料


Copyright © 2010-2022 Wen-Tien Chang All Rights Reserved.