路由(Routing)

最后更新于:2022-04-01 02:31:23

> Weeks of programming can save you hours of planning. – Unknown 不同於靜態網頁的路由是直接對應於檔案的目錄結構,一個Web開發框架會將路由功能納入其中,來獲得最大的彈性。也就是您可以指定任意URL對應到任一個Controller的Action。另一方面,我們也不在Views中直接寫死URL網址,而是透過Helper輔助方法根據你的路由設定來產生URL,這樣也可以確定該網址一定有對應的Controller和Action,不然就會出現NoMethodError找不到Helper方法的錯誤。 也就是,路由系統做幾件事情: 1\. 辨識HTTP Request的URL網址,然後對應到設定的Controller Action。 2\. 處理網址內的參數字串,例如:/users/show/123送到Users controller的show action時,會將`params[:id]` 設定為 123 3\. 辨識link_to和redirect_to的參數產生URL字串,例如 ~~~ link_to 'hola!', { :controller=> 'welcome', :action => 'say' } ~~~ 會產生 ~~~ <a href="/welcome/say">hola!</a> ~~~ Rails這麼彈性的路由功能,可以怎麼用呢?例如設計一個部落格網站,如果是沒有使用框架的CGI或PHP網頁開發,會長得這樣: ~~~ 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 controller的show 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 controller的say action。外卡路由是一種非常簡便的對應方式。這種方式的缺點當網站的Action變多的時候,會容易讓Controller的設計變得混亂沒有規則。稍後介紹的RESTful路由則是Rails對此提出的組織路由方案。 還有,`(.format)`這一段則會讓路由可以接受`.json`、`.xml`等有副檔名的網址,並且轉成`params[:format]`參數傳進Controller裡,搭配`respond_to`而回傳不同的格式。 ## 命名路由Named Routes Named Routes可以幫助我們產生URL helper如`meetings_url`或`meetings_path`,而不需要用`{:controller => 'meetings', :action => 'index'}`的方式: ~~~ get '/meetings' => 'events#index', :as => "meetings" ~~~ 其中`:as`的部份就會產生一個`meetings_path`和`meetings_url`的Helpers,`_path`和`_url`的差別在於前者是相對路徑,後者是絕對路徑。一般來說比較常用`_path`方法,除非像是在Email信件中,才必須用`_url`提供包含Domain的完整網址。 > 雖然RESTful已經是設計Rails最常見的路徑模式,但是在一些特殊的情況、不符合CRUD模型的情結就不一定適用了,例如有多重步驟的表單(又叫作Wizard) 時,使用命名路由反而會比較簡潔,例如`step1_path, step2_path, step3_path`等。 ## Redirect 在路由中可以直接設定轉向: ~~~ get "/foo" => redirect("/bar") get "/ihower" => redirect("http://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我們的路由規則,將共通的controller、constraints、網址前置path和URL 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 helper是`bar_meeting_url`和`bar_meetings_url`。 ### Scope Module Module參數則可以讓Controller分Module,例如 ~~~ scope :path => '/api/v1/', :module => "api_v1", :as => 'v1' do resources :projects end ~~~ 如此controller會是`ApiV1::ProjectsController`,網址如/api/v1/projects,而URL Helper如`v1_projects_path`這樣的形式。 ### 領域名稱Namespace Namespace是Scope的一種特定應用,特別適合例如後台介面,這樣就整組`controller`、網址`path`、URL Helper前置名稱`都影響到: ~~~ namespace :admin do resources :projects end ~~~ 如此controller會是`Admin::ProjectsController`,網址如/admin/projects,而URL Helper如`admin_projects_path`這樣的形式。 ## 特殊條件限定 我們可以利用`: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 Helper如`project_tasks_path(@project)`和`project_task_path(@project, @task)`,它的網址會如projects/123/tasks和projects/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_path`和`on_offer_products_path`這兩個URL Helper,產生出如products/sold和products/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這樣的網址。 ### 限定部分支援 透過`except`或`only`參數,我們不一定要啟用預設的七個Resource路由,例如 ~~~ resource :events, :except => [:index, :show] resource :events, :only => :create ~~~ ### PATCH v.s. PUT PATCH是一個相對新的HTTP verb,Rails為了保持相容性這兩個HTTP verbs都會進到update action之中。而編輯表單預設則是用PATCH。在REST語意上的差別是: * PATCH 用於修改部分資料 * PUT 用來替換資料(replace) 對HTTP API設計有興趣的讀者,可以參考[http://ihower.tw/blog/archives/6483](http://ihower.tw/blog/archives/6483)一文。 ## rake routes 如果你不清楚這些路由設定到底最後的規則是什麼,你可以執行: ~~~ rake routes ~~~ 這樣就會產生出所有URL Helper、URL 網址和對應的Controller Action都列出來。 ## 常見錯誤 ### Routing Error 當URL找不到任何路由規則可以符合時,會出現這個錯誤。例如一個GET的路由,你用`button_to`送出POST,這樣就不符合規則。 ### ActionController::UrlGenerationError 當一個路由Helper的參數不夠的時候,會出現這個錯誤。例如`event_path(event)`這個方法的event參數不能是`nil`。如果你打錯成`event_path(@events)`而`@events`是個`nil`,就會出現這個錯誤。 ## 結論 透過RESTful和Named Route,我們就不再需要透過外卡路由的Hash來指定路由了。所有的路由規則都可以在routes.rb一目了然。 ## 線上參考資料 * [Rails Routing from the Outside In](http://guides.rubyonrails.org/routing.html)
';