Update(2006/12/4): 一些小修改跟 link_to 的 web accessibility 補充。
Update(2006/12/11): 請接著看系列文章下一集: Rails RESTful 相關工具。
Rails RESTful 系 列 彈第三篇,我想 David Heinemeier Hansson 的這篇投影片是很好的開場白: Discovering a world of Resources on Rails
本來這篇想寫的仔細點,不過後來我發現AWDwR第二版講 Resource-Based Routing 的頁數還不少(十幾頁吧,還蠻詳細的),要用 Rails 的人應該都去買一本,我想我這裡就不巨細靡遺了… :p
在 Rails 中是如何實作 RESTful 支援的? 首先是在 Resource Routes 上,在 routes.rb 加入
map.resources :users
這樣的宣告將在自動對應 URL路徑跟 Controller 的 action ,而有以下的結果 :
GET: /users => [:action => ‘index’]
GET: /users.xml => [:action => ‘index’, :format => ‘xml’]
GET: /users/1 => [:action => ‘show’, :id => 1]
GET: /users/1;edit => [:action => ‘edit’, :id => 1]
GET: /users/1.xml => [:action => ‘show’, :id => 1, :format => ‘xml’]
POST: /users => [:action => ‘create’]
PUT: /users/1 => [:action => ‘update’, :id => 1]
DELETE: /users/1 => [:action => ‘destroy’, :id => 1]
也就是使用 named routes 來實作出 verb-oriented controllers,單一個 resource 根據 HTTP verb 而有不同的行為。
Resource method 也接受一些進階參數,例如:
map.resources :articles,
:collection => {:sort => :put}, #客製 method 給群集,並指定用哪種 HTTP verb ( :get/:post/:put/:delete或:any)
:member => {:deactivate => :delete}, # 客製 method 給某元素
:new => {:preview => :post}, #客製 method 給新元素
:controller => ‘articles’, #指定 Controller
:singular => ‘article’, #指定用單數
:path_prefix => ‘/teams/:team_id’, #在routes前加上route variables,舉例如下。
:name_prefix => ‘my_’ #在產生的 helper 前加prefix,通常配合 path_prefix 或 nested 使用避免名稱衝突。
上述的 path_prefix 可以改用Nested 用法,例如:
map.resources :teams do |teams|
teams.resources :players
end
產生的對應效果是
GET: /teams/13/players/1 => [:controller => ‘players’, :action => ‘show’, :team_id => 13, :id => 1]
除了產生對應 routes 的 action 給 Controller,最方便的是自動產生了一些 helpers 給 views,例如 (這裡的team,player根據你的resource name而自行替換,注意單複數):
helpers | HTTP Verb | 產生的Path | 對應的Action |
---|---|---|---|
teams_path | GET | /teams | index |
team_path(id) | GET | /teams/1 | show |
new_team_path | GET | /teams/new | new |
teams_path | POST | /teams | create |
edit_team_path(id) | GET | /teams/1;edit | edit |
team_path(id) | PUT | /teams/1 | update |
team_path(id) | DELETE | /teams/1 | destroy |
而 Nested Resource 的用法舉例:
helpers | 產生的Path |
---|---|
players_path(@team) | /teams/1/players |
player_path(@team, @player ) | /teams/1/players/5 |
有了這些 helper,我們就可以這樣產生按鈕、超連結、Ajax超連結跟表單 :
button_to “Destroy”, team_path(@team), :confirm => “Are you sure?”, :method => :delete
link_to “Destroy”, team_path(@team), :confirm => “Are you sure?”, :method => :delete
link_to_remote “Destroy”, :url => team_path(@team), :confirm => “Are you sure?”, :method => :delete
form_for :team, @team, :url => team_path(@team), :html => { :method => :put } do |f| …
其中因為瀏覽器不支援 PUT 跟 DELETE,所以是 link_to 是用 Javascript + DOM 在按下動作時生成表單 form 跟 _method 參數用 HTTP POST 來模擬,而 form_for 是在 HTML 中加上隱藏的 _method 參數。若你關心 web accessibility 議題,那麼 link_to 除了HTTP GET之外都應改用 button_to,因為這樣即使 Javascript 關閉了還可以正確送出(因為 button_to 的 _method 參數是 hard-code 在HTML form裡)。anyway… 這些都被 helper 隱藏起來了,用起來不用擔心,要指定 HTTP verb 時只需設定 :method 參數即可。
整理一下Resourse的 named routes,可以分成三種情況: collection 群集、member 某個元素、new 新元素,每種又可以搭配不同 HTTP verb,然後與 Controller 裡的 action 配對。例如預設 /teams + GET 對應 action index、/teams + POST 對應 action create、/teams/1 預設對應 action show、 /teams/new 預設對應 action new等。
現實狀況中除了 GET/POST/PUT/DELETE 這些 HTTP verb之外,對該 resourse 還會需要其它客製處理,因此這三類分別還可以加 method 上去,首先在 routes.rb 對該 resource 加設定: 指定哪種情況(:collection/:member/:new)、搭配的 Controller action 是哪個、用什麼 HTTP verb,這樣就會有新的 named routes 出來了,舉例如下:
客製出 helpers | 產生的 Path | 對應的Action | Map Options的設定 | 使用的HTTP Verb |
---|---|---|---|---|
sort_tags_path | /tags;sort | sort | :collection => { :sort => :put } | PUT |
deactivate_tag_path(id) | /tag/1;deactivate | deactivate | :member => { :deactivate => :delete } | DELETE |
preview_new_tag_path | /tags/new;preview | preview | :new => { :preview => :post } | POST |
你可能會覺得 new 的角色有點奇怪,想了一下應該這樣看: new是針對一個(資料庫中)還不存在且沒有id的新元素,而 member 是針對已經存在有id的。
RESTful 三位一體的一角是 format 格式,所以你可以用 respond_to 來設定不同格式要怎麼處理:
respond_to { |wants| wants.all | .text | .html | .js | .ics | .xml | .rss | .atom | .yaml }
它會根據 client 的請求(HTTP header的 Accept 或 Content-Type 或 URL中副檔名的不同)來決定回應的格式,例如:
helpers | 產生的Path |
---|---|
formatted_teams_path(:xml) | /teams.xml |
formatted_team_path(id,:rss) | /teams/1.rss |
formatted_players_path(@team,:atom) | /teams/1/players.atom |
formatted_player_path(@team,@player, :js) | /teams/1/players/5.js |
formatted_player_path(:team_id => 1, :id => 5, :format => :js ) | /teams/1/players/5.js |
這樣看下來 Rails RESTful 除了有其重要意義之外,也帶來了非常多的慣例來簡化開發程序。看似規則多,其實組合彈性大,好處很多(Consistency/Simplicity/Discoverability)。不過魔術變多了,在一般沒有 RESTful 概念的前提下,一開始要上手的難度也提高了(會有很多why這樣做的疑問…:p)。
by the way… G小R裝用功 也寫了一篇 Rails RESTful 初探 可以參考看看。
參考資料:
我那篇只是粗淺的摸一摸,然後把看到的東西記錄一下而已
相較之下這篇真是太詳細了 又讓我搞懂了一些原本不清楚的東西 獲益良多