Ruby on Rails : RESTful 和 Ajax

9,876
-1

Published on

2 Comments
11 Likes
Statistics
Notes
No Downloads
Views
Total Views
9,876
On Slideshare
0
From Embeds
0
Number of Embeds
2
Actions
Shares
0
Downloads
199
Comments
2
Likes
11
Embeds 0
No embeds

No notes for slide

Ruby on Rails : RESTful 和 Ajax

  1. 1. Ruby on Rails Part3: RESTful & Ajax ihower@gmail.com http://creativecommons.org/licenses/by-nc/2.5/tw/
  2. 2. Browser MVC Model-View-Control HTTP request GET /users/1 route.rb UsersController Model def show @user = User.find(params[:id]) respond_to do |format| Database format.html format.xml end end #show.html.erb View <html> def index <h1>User Profile</h1> ...... <p><%= @user.nickname %></p> end </html> end
  3. 3. Browser Controller Action MVC Model-View-Control HTTP request GET /users/1 route.rb UsersController Model def show @user = User.find(params[:id]) respond_to do |format| Database format.html format.xml end end #show.html.erb View <html> def index <h1>User Profile</h1> ...... <p><%= @user.nickname %></p> end </html> end
  4. 4. Representational State Transfer ( REST) • Roy Fielding 2000 • SOAP XML-RPC • Web Service API Amazon Yahoo! Google API
  5. 5. Rails RESTful ?
  6. 6. RESTful : designing controller and action is chaos
  7. 7. controller class EventsController < ApplicationController index show new create edit update destroy
  8. 8. controller class EventsController < ApplicationController index watch_list show add_favorite new invite create join edit leave update white_member_list destroy black_member_list feeds deny_user add_comment allow_user show_comment edit_managers destroy_comment set_user_as_manager edit_comment set_user_as_member approve_comment ..... mark_comment_as_spam etc.
  9. 9. controller class EventsController < ApplicationController index watch_list show add_favorite new create controller ??? invite join edit update actions leave ???? white_member_list destroy controller actions black_member_list ??? feeds deny_user add_comment allow_user show_comment edit_managers destroy_comment set_user_as_manager edit_comment set_user_as_member approve_comment ..... mark_comment_as_spam etc.
  10. 10. named routes # routes.rb map.connect '/:controller/:action/:id' <%= link_to ‘text’, :controller => ‘events’, :action => ‘show’, :id => event.id %>
  11. 11. named routes # routes.rb map.connect '/:controller/:action/:id' <%= link_to ‘text’, :controller => ‘events’, :action => ‘show’, :id => event.id %> named route
  12. 12. named routes # routes.rb map.connect '/:controller/:action/:id' <%= link_to ‘text’, :controller => ‘events’, :action => ‘show’, :id => event.id %> named route # routes.rb map.event '/events/:id', :controller => 'event', :action => 'show'
  13. 13. named routes # routes.rb map.connect '/:controller/:action/:id' <%= link_to ‘text’, :controller => ‘events’, :action => ‘show’, :id => event.id %> named route # routes.rb map.event '/events/:id', :controller => 'event', :action => 'show' <%= link_to ‘text’, event_path(event) %>
  14. 14. named routes # routes.rb map.connect '/:controller/:action/:id' <%= link_to ‘text’, :controller => ‘events’, hmm.... named routes :action => ‘show’, event_delete_path? :id => event.id %> event_create_path? named route events_path? # routes.rb events_new_path? map.event '/events/:id', :controller => 'event', :action => 'show' <%= link_to ‘text’, event_path(event) %>
  15. 15. : controllers actions!!
  16. 16. The ideas from CRUD...
  17. 17. HTTP methods (RFC 2616) POST GET PUT DELETE Create Read Update Delete
  18. 18. HTTP methods (RFC 2616) POST GET PUT DELETE Create Read Update Delete GET is defined as a safe method
  19. 19. /events/create /events/show/1 /events/update/1 /events/destroy/1
  20. 20. /events/create Add HTTP method /events/show/1 /events/update/1 /events/destroy/1
  21. 21. /events/create Add HTTP method /events/show/1 /events/update/1 /events/destroy/1
  22. 22. /events/create POST /events Add HTTP method /events/show/1 GET /events/1 /events/update/1 PUT /events/1 /events/destroy/1 DELETE /events/1
  23. 23. /events/create POST /events Add HTTP method /events/show/1 GET /events/1 /events/update/1 PUT /events/1 /events/destroy/1 DELETE /events/1 Remove actions from URL, and we have simple named route.
  24. 24. CRUD-based action names get things simpler create show update delete POST GET PUT DELETE
  25. 25. CRUD-based action names get things simpler create show update delete POST GET PUT DELETE controller CRUD
  26. 26. routes.rb ActionController::Routing::Routes.draw do |map| map.resources :events end named routes actions
  27. 27. routes.rb ActionController::Routing::Routes.draw do |map| map.resources :events end a resource is something with URL named routes actions
  28. 28. routes.rb ActionController::Routing::Routes.draw do |map| map.resources :events end a resource is something with URL named routes actions 4
  29. 29. routes.rb ActionController::Routing::Routes.draw do |map| map.resources :events end a resource is something with URL named routes actions 4 7
  30. 30. routes.rb ActionController::Routing::Routes.draw do |map| map.resources :events end a resource is something with URL named routes actions 4 7
  31. 31. routes.rb ActionController::Routing::Routes.draw do |map| map.resources :events end a resource is something with URL named routes actions 4 7 4 HTTP method
  32. 32. RESTful CRUD
  33. 33. index The default request method is GET <%= link_to ‘event list’, events_path %> class EventsController < ApplicationController def index @events = Event.find(:all) end ... end
  34. 34. show The default request method is GET <%= link_to event.name, event_path(event) %> class EventsController < ApplicationController def show @event = Event.find(params[:id]) end ... end
  35. 35. new/create <%= link_to ‘new event’, new_event_path %> class EventsController < ApplicationController def new @event = Event.new end end
  36. 36. new/create <%= link_to ‘new event’, new_event_path %> class EventsController < ApplicationController def new @event = Event.new end end <% form_for @event, :url => events_path do |f| %> <%= f.text_field :name %> <%= f.submit "Create" %> <% end %>
  37. 37. new/create <%= link_to ‘new event’, new_event_path %> class EventsController < ApplicationController def new @event = Event.new end end <% form_for @event, :url => events_path do |f| %> <%= f.text_field :name %> <%= f.submit "Create" %> <% end %>
  38. 38. new/create <%= link_to ‘new event’, new_event_path %> class EventsController < ApplicationController def new In a form, the default @event = Event.new request method is end POST end <% form_for @event, :url => events_path do |f| %> <%= f.text_field :name %> <%= f.submit "Create" %> <% end %>
  39. 39. new/create <%= link_to ‘new event’, new_event_path %> class EventsController < ApplicationController def new In a form, the default @event = Event.new request method is end POST end <% form_for @event, :url => events_path do |f| %> <%= f.text_field :name %> <%= f.submit "Create" %> <% end %> class EventsController < ApplicationController def create Event.create(params[:id]) end end
  40. 40. edit/update <%= link_to event.name, edit_event_path(event) %> class EventsController < ApplicationController def edit @event = Event.find(params[:id]) end end
  41. 41. edit/update <%= link_to event.name, edit_event_path(event) %> class EventsController < ApplicationController def edit @event = Event.find(params[:id]) end end <% form_for @event, :url => event_path(@event), :method => :put do |f| %> <%= f.text_field :name %> <%= f.submit "Create" %> <% end %>
  42. 42. edit/update <%= link_to event.name, edit_event_path(event) %> class EventsController < ApplicationController def edit @event = Event.find(params[:id]) end end <% form_for @event, :url => event_path(@event), :method => :put do |f| %> <%= f.text_field :name %> <%= f.submit "Create" %> <% end %>
  43. 43. edit/update <%= link_to event.name, edit_event_path(event) %> class EventsController < ApplicationController def edit @event = Event.find(params[:id]) end end <% form_for @event, :url => event_path(@event), :method => :put do |f| %> <%= f.text_field :name %> <%= f.submit "Create" %> the request <% end %> method is PUT
  44. 44. edit/update <%= link_to event.name, edit_event_path(event) %> class EventsController < ApplicationController def edit @event = Event.find(params[:id]) end end <% form_for @event, :url => event_path(@event), :method => :put do |f| %> <%= f.text_field :name %> <%= f.submit "Create" %> the request <% end %> method is PUT class EventsController < ApplicationController def create Event.create(params[:event]) end end
  45. 45. the request destroy method is DELETE <%= link_to @event, event_path(@event), :method => :delete %> class EventsController < ApplicationController def destroy Event.find(params[:id]).destroy end ... end
  46. 46. 4 HTTP methods, 4 URL helper, 7 actions Helper GET POST PUT DELETE /events/1 /events/1 /events/1 event_path(@event) show update destroy /events /events events_path index create /events/1/edit edit_event_path(@event) edit /events/new new_events_path new
  47. 47. Singular and Plural RESTful Routes • show, new, edit, destroy • index, new, create
  48. 48. Singular and Plural RESTful Routes • show, new, edit, destroy • index, new, create event_path(@event)
  49. 49. Singular and Plural RESTful Routes • show, new, edit, destroy • index, new, create event_path(@event) HTTP verb show, update, destroy
  50. 50. Singular and Plural RESTful Routes • show, new, edit, destroy • index, new, create event_path(@event) HTTP verb show, update, destroy events_path
  51. 51. Singular and Plural RESTful Routes • show, new, edit, destroy • index, new, create event_path(@event) HTTP verb show, update, destroy events_path HTTP verb index, create
  52. 52. [custom route]_event[s]_path( event )
  53. 53. new, edit [custom route]_event[s]_path( event )
  54. 54. ? new, edit ? [custom route]_event[s]_path( event )
  55. 55. _path ? _url http://domain/ new, edit ? [custom route]_event[s]_path( event )
  56. 56. _path ? _url http://domain/ new, edit ? [custom route]_event[s]_path( event ) :method => GET | POST | PUT | DELETE
  57. 57. map.connect ':controller/:action/:id'
  58. 58. map.connect ':controller/:action/:id'
  59. 59. map.connect ':controller/:action/:id' link_to event.name, :controller => ‘events’, :action => :show , :id => event.id
  60. 60. map.connect ':controller/:action/:id' link_to event.name, :controller => ‘events’, :action => :show , :id => event.id
  61. 61. map.connect ':controller/:action/:id' link_to event.name, :controller => ‘events’, :action => :show , :id => event.id
  62. 62. map.connect ':controller/:action/:id' link_to event.name, :controller => ‘events’, :action => :show , :id => event.id link_to event.name, event_path(event) resources URL Helper
  63. 63. PUT? DELETE?
  64. 64. The PUT&DELETE Cheat
  65. 65. The PUT&DELETE Cheat • PUT&DELETE method
  66. 66. The PUT&DELETE Cheat • PUT&DELETE method • Rails _method
  67. 67. The PUT&DELETE Cheat • PUT&DELETE method • Rails _method <form id="edit_events_1" method="post" action="/events/1"> <input type="hidden" value="put" name="_method"/> .... </form>
  68. 68. The PUT&DELETE Cheat • PUT&DELETE method • Rails _method <form id="edit_events_1" method="post" action="/events/1"> <input type="hidden" value="put" name="_method"/> .... </form>
  69. 69. The PUT&DELETE Cheat • PUT&DELETE method • Rails _method <form id="edit_events_1" method="post" action="/events/1"> <input type="hidden" value="put" name="_method"/> .... </form> <a onclick="var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute ('name', '_method'); m.setAttribute('value', 'delete'); f.appendChild(m);f.submit ();return false;" href="/events/1">Destroy</a>
  70. 70. The PUT&DELETE Cheat • PUT&DELETE method • Rails _method <form id="edit_events_1" method="post" action="/events/1"> <input type="hidden" value="put" name="_method"/> .... </form> <a onclick="var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute ('name', '_method'); m.setAttribute('value', 'delete'); f.appendChild(m);f.submit ();return false;" href="/events/1">Destroy</a>
  71. 71. The Problem
  72. 72. The Problem • HTML GET/POST HTML forms PUT/DELETE
  73. 73. The Problem • HTML GET/POST HTML forms PUT/DELETE • XmlHttpRequest ( Ajax request) GET/POST/PUT/DELETE/HEAD/OPTIONS
  74. 74. The Problem • HTML GET/POST HTML forms PUT/DELETE • XmlHttpRequest ( Ajax request) GET/POST/PUT/DELETE/HEAD/OPTIONS • Firefox/Safari
  75. 75. The Problem • HTML GET/POST HTML forms PUT/DELETE • XmlHttpRequest ( Ajax request) GET/POST/PUT/DELETE/HEAD/OPTIONS • Firefox/Safari • Opera PUT/DELETE
  76. 76. The Problem • HTML GET/POST HTML forms PUT/DELETE • XmlHttpRequest ( Ajax request) GET/POST/PUT/DELETE/HEAD/OPTIONS • Firefox/Safari • Opera PUT/DELETE • IE DELETE !
  77. 77. As a Ruby On Rails special, Prototype also reacts to other verbs (such as 'put' and 'delete' by actually using 'post' and putting an extra '_method' parameter with the originally requested method in there.)
  78. 78. The type of request to make ("POST" or "GET"), default is "GET". Note: Other HTTP request methods, such as PUT Text and DELETE, can also be used here, but they are not supported by all browsers.
  79. 79. 4 HTTP methods, 4 URL helper, 7 actions Helper GET POST PUT DELETE /events/1 /events/1 /events/1 event_path(@event) show update destroy /events /events events_path index create /events/1/edit edit_event_path(@event) edit /events/new new_events_path new It’s beauty, but RESTful can handle the real & complex world ?
  80. 80. standardization on action name The heart of the Rails’s REST support is a technique for creating bundles of named routes automatically From Rails Way Chap.4
  81. 81. RESTful Scaffold
  82. 82. respond_to ?
  83. 83. One Action, Multiple Response Formats def index @users = User.find(:all) respond_to do |format| format.html # index.html.erb format.xml { render :xml => @user.to_xml } end end
  84. 84. format.html
  85. 85. format.html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> </head> <body> <p> ihower at 2008-01-19 </p> </body> </html>
  86. 86. format.html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> </head> <body> <p> ihower at 2008-01-19 </p> </body> </html> format.xml
  87. 87. format.html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> </head> <body> <p> ihower at 2008-01-19 </p> </body> </html> format.xml <?xml version="1.0" encoding="UTF-8"?> <user> <created-at type="datetime">2008-01-19T09:55:32+08:00</created-at> <id type="integer">2</id> <name>ihower</name> <updated-at type="datetime">2008-01-19T09:55:32+08:00</updated-at> </user>
  88. 88. you don't need this! def show_html @users = User.find(:all) end def show_xml @users = User.find(:all) render :xml => @user.to_xml end def show_json @user = User.find(:all) render :json => @user.to_json end
  89. 89. you don't need this! def show_html @users = User.find(:all) end def show_xml @users = User.find(:all) render :xml => @user.to_xml end def show_json @user = User.find(:all) render :json => @user.to_json end
  90. 90. you don't need this! def show_html @users = User.find(:all) end def show_xml @users = User.find(:all) render :xml => @user.to_xml end def show_json @user = User.find(:all) render :json => @user.to_json end
  91. 91. action Don’t repeat yourself
  92. 92. formats • format.html • format.csv • format.xml • format.xls • format.js • format.yaml • format.json • format.txt • format.atom • more.... • format.rss
  93. 93. http://registrano.com/events/5381ae/attendees
  94. 94. http://registrano.com/events/5381ae/attendees
  95. 95. http://registrano.com/events/5381ae/attendees http://registrano.com/events/5381ae/attendees.csv
  96. 96. http://registrano.com/events/5381ae/attendees http://registrano.com/events/5381ae/attendees.csv http://registrano.com/events/5381ae/attendees.xls
  97. 97. ( UI )
  98. 98. XML API JSON API ( UI )
  99. 99. XML API JSON API ( UI ) Adobe Flex
  100. 100. Rails: how to know?
  101. 101. URL http://localhost:3000/users.xml template <%= link_to ‘User List’, formatted_users_path(:xml) %> <a href=”/users.xml”>User List</a>
  102. 102. HTTP request Headers GET /users HTTP/1.1 Host: localhost:3000 User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; zh-TW; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13 Accept: text/javascript, text/html, application/xml, text/xml, */* Accept-Language: zh-tw,en-us;q=0.7,en;q=0.3 Accept-Encoding: gzip,deflate Accept-Charset: Big5,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive X-Requested-With: XMLHttpRequest X-Prototype-Version: 1.6.0.1
  103. 103. HTTP request Headers GET /users HTTP/1.1 Host: localhost:3000 User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; zh-TW; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13 Accept: text/javascript, text/html, application/xml, text/xml, */* Accept-Language: zh-tw,en-us;q=0.7,en;q=0.3 Accept-Encoding: gzip,deflate Accept-Charset: Big5,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Javascript Ajax X-Requested-With: XMLHttpRequest request X-Prototype-Version: 1.6.0.1
  104. 104. • params[:format] GET /users/1?format=xml
  105. 105. • Controller code class ApplicationController < ActionController::Base before_filter :adjust_format_for_iphone helper_method :iphone_user_agent? protected def adjust_format_for_iphone request.format = :iphone if iphone_user_agent? || iphone_subdomain? end # Request from an iPhone or iPod touch? # (Mobile Safari user agent) def iphone_user_agent? request.env["HTTP_USER_AGENT" ] && request.env["HTTP_USER_AGENT" ][/(Mobile/.+Safari)/] end def iphone_subdomain? return request.subdomains.first == "iphone" end end
  106. 106. custom format # config/initializers/mime_types.rb Mime::Type.register ‘audio/mpeg’, :mp3?Mime::Type.register ‘audio/mpegurl’, :m3u
  107. 107. custom format # config/initializers/mime_types.rb Mime::Type.register ‘audio/mpeg’, :mp3?Mime::Type.register ‘audio/mpegurl’, :m3u def show @mp3 = Mp3.find(params[:id]) respond_to do |format| format.html format.mp3 { redirect_to @mp3.url } format.m3u { render :text => @mp3.url } end end
  108. 108. custom format # config/initializers/mime_types.rb Mime::Type.register ‘audio/mpeg’, :mp3?Mime::Type.register ‘audio/mpegurl’, :m3u http://localhost:3000/mp3s/1.mp3 def show @mp3 = Mp3.find(params[:id]) respond_to do |format| format.html format.mp3 { redirect_to @mp3.url } format.m3u { render :text => @mp3.url } end end
  109. 109. custom format # config/initializers/mime_types.rb Mime::Type.register ‘audio/mpeg’, :mp3?Mime::Type.register ‘audio/mpegurl’, :m3u http://localhost:3000/mp3s/1.mp3 def show @mp3 = Mp3.find(params[:id]) respond_to do |format| format.html format.mp3 { redirect_to @mp3.url } format.m3u { render :text => @mp3.url } end end
  110. 110. template ?
  111. 111. template • format (minetype) template generator (renderer) • Rails2 action.minetype.renderer filename.html.erb
  112. 112. template def index @users = User.find(:all) respond_to do |format| format.html # index.html.erb format.xml # index.xml.builder end end
  113. 113. template def index @users = User.find(:all) respond_to do |format| format.html # index.html.erb format.xml # index.xml.builder end end
  114. 114. erb template • ruby code • HTML ( format.html) <h1><%= @event.name %></h1>
  115. 115. erb template • ruby code • HTML ( format.html) <h1><%= @event.name %></h1>
  116. 116. erb template • ruby code • HTML ( format.html) <h1><%= @event.name %></h1> <h1>OSDC 2008</h1>
  117. 117. erb template • ruby code • HTML ( format.html) <h1><%= @event.name %></h1> show.html.erb <h1>OSDC 2008</h1>
  118. 118. builder template • Ruby XML xml.instruct! xml.title "This is a title" xml.person do xml.first_name "Ryan" xml.last_name "Raaum" end
  119. 119. builder template • Ruby XML xml.instruct! xml.title "This is a title" xml.person do xml.first_name "Ryan" xml.last_name "Raaum" end
  120. 120. builder template • Ruby XML xml.instruct! xml.title "This is a title" xml.person do xml.first_name "Ryan" xml.last_name "Raaum" end <?xml version="1.0" encoding="UTF-8"?> <title>This is a title</title> <person> <first_name>Ryan</first_name> <last_name>Raaum</last_name> </person>
  121. 121. builder template • Ruby XML xml.instruct! xml.title "This is a title" xml.person do xml.first_name "Ryan" xml.last_name "Raaum" end show.xml.builder <?xml version="1.0" encoding="UTF-8"?> <title>This is a title</title> <person> <first_name>Ryan</first_name> <last_name>Raaum</last_name> </person>
  122. 122. builder template (cont.) • Atom feed Rails2 Atom helper atom_feed do |feed| feed.title( @feed_title ) feed.updated((@events.first.created_at)) for event in @events feed.entry(event) do |entry| index.atom.builder entry.title(event.title) entry.content(event.description, :type => 'html') entry.author do |author| author.name( event.creator.nickname ) end end end end
  123. 123. Ajax on Rails
  124. 124. Ajax link_to_remote ‘Terms’, :url => terms_path, :update => ‘content’ <a onclick="$.ajax({async:true, beforeSend:function(xhr) {xhr.setRequestHeader ('Accept', 'text/html, */*')}, complete:function(request){ $("#content").html(request.responseText);}, dataType:'html', type:'get', url:'/terms'}); return false;" href="/terms"> </a> <div id=”content”> #content Browser </div> HTML <h1>ABC</j1> Ajax format.html <ul> <li>1</li> <li>2</li> </ul> Server
  125. 125. Ajax (1)
  126. 126. Ajax <a onclick="$.ajax({async:true, beforeSend:function(xhr) {xhr.setRequestHeader ('Accept', 'text/javascript, text/html, application/xml, text/xml, */*')}, dataType:'script', type:'get', url:'/user/1'}); return false;>User</a> <div id=”content”> </div> Javascript Browser $("#content").html(ʻ blahʼ); Ajax format.js $(“#sidebar”).html(ʻ blahʼ); $("#content").effect("highlight"); Server
  127. 127. RJS template <%= link_to_remote ‘ajax show’, :url => event_path(@event) %>
  128. 128. RJS template <%= link_to_remote ‘ajax show’, :url => event_path(@event) %>
  129. 129. RJS template <%= link_to_remote ‘ajax show’, :url => event_path(@event) %> def show @event = Event.find(params[:id]) respond_to |format| format.js end end
  130. 130. RJS template <%= link_to_remote ‘ajax show’, :url => event_path(@event) %> def show @event = Event.find(params[:id]) • respond_to |format| format.js end Ruby Javascript end
  131. 131. RJS template <%= link_to_remote ‘ajax show’, :url => event_path(@event) %> def show @event = Event.find(params[:id]) • respond_to |format| format.js end Ruby Javascript end # show.js.rjs page.replace_html ‘content’, :partial =>’event’ page.visual_effect :highlight, ‘ content’
  132. 132. RJS template <%= link_to_remote ‘ajax show’, :url => event_path(@event) %> def show @event = Event.find(params[:id]) • respond_to |format| format.js end Ruby Javascript end # show.js.rjs page.replace_html ‘content’, :partial =>’event’ page.visual_effect :highlight, ‘ content’
  133. 133. RJS template <%= link_to_remote ‘ajax show’, :url => event_path(@event) %> def show @event = Event.find(params[:id]) • respond_to |format| format.js end Ruby Javascript end # show.js.rjs page.replace_html ‘content’, :partial =>’event’ page.visual_effect :highlight, ‘ content’
  134. 134. RJS template <%= link_to_remote ‘ajax show’, :url => event_path(@event) %> def show @event = Event.find(params[:id]) • respond_to |format| format.js end Ruby Javascript end # show.js.rjs page.replace_html ‘content’, :partial =>’event’ page.visual_effect :highlight, ‘ content’ try { new Element.update("content", "blah"); new Effect.Highlight("content",{}); } catch (e) { alert('RJS error:nn' + e.toString()); alert('new Element.update ("content", "blah");nnew Effect.Highlight("content",{});'); throw e }
  135. 135. RJS template <%= link_to_remote ‘ajax show’, :url => event_path(@event) %> def show @event = Event.find(params[:id]) • respond_to |format| format.js end Ruby Javascript end # show.js.rjs page.replace_html ‘content’, :partial =>’event’ page.visual_effect :highlight, ‘ content’ Browser Server Javascript code try { new Element.update("content", "blah"); new Effect.Highlight("content",{}); } catch (e) { alert('RJS error:nn' + e.toString()); alert('new Element.update ("content", "blah");nnew Effect.Highlight("content",{});'); throw e }
  136. 136. Ajax (2)
  137. 137. inline RJS def show @note = Note.find(params[:id]) respond_to |format| format.js { render :update do |page| page.replace_html ‘content’, :partial =>’note’ page.visual_effect :highlight, ‘ content’ page << ‘alert(“hello world!”);’ end } end end
  138. 138. inline RJS def show @note = Note.find(params[:id]) respond_to |format| format.js { render :update do |page| page.replace_html ‘content’, :partial =>’note’ page.visual_effect :highlight, ‘ content’ page << ‘alert(“hello world!”);’ end } end end
  139. 139. inline RJS def show @note = Note.find(params[:id]) respond_to |format| format.js { render :update do |page| page.replace_html ‘content’, :partial =>’note’ page.visual_effect :highlight, ‘ content’ page << ‘alert(“hello world!”);’ end } end JavaScript end
  140. 140. js.erb template <%=link_to_remote ‘ajax’, :url => posts_path %> def index ... • JavaScript • Rails 1.x respond_to |format| end format.js hack! end (google MinusMOR plugin) # index.js.erb $j("#foo").html(<%= (render :partial => 'note.html').to_json %>); $j("#foo").Highlight();
  141. 141. respond_to : Graceful Degradation def index @users = User.find(:all) respond_to do |format| format.js { render :update do |page| page.replace_html ‘content’, ‘<p>blah</p>’ end } format.html #index.html.erb end end <a href=”/users” onclick=”$.ajax(...blah...);return false;”>
  142. 142. respond_to : Graceful Degradation def index @users = User.find(:all) respond_to do |format| format.js { render :update do |page| page.replace_html ‘content’, ‘<p>blah</p>’ end } format.html #index.html.erb end end <a href=”/users” onclick=”$.ajax(...blah...);return false;”>
  143. 143. respond_to : Graceful Degradation def index @users = User.find(:all) respond_to do |format| format.js { render :update do |page| page.replace_html ‘content’, ‘<p>blah</p>’ end } format.html #index.html.erb end end Browser Javascript <a href=”/users” onclick=”$.ajax(...blah...);return false;”>
  144. 144. respond_to : Graceful Degradation def index @users = User.find(:all) respond_to do |format| format.js { render :update do |page| page.replace_html ‘content’, ‘<p>blah</p>’ end } format.html #index.html.erb end end Browser Javascript <a href=”/users” onclick=”$.ajax(...blah...);return false;”>
  145. 145. respond_to : Graceful Degradation def index @users = User.find(:all) respond_to do |format| format.js { render :update do |page| page.replace_html ‘content’, ‘<p>blah</p>’ end } format.html #index.html.erb end end Browser Javascript Browser Javascript <a href=”/users” onclick=”$.ajax(...blah...);return false;”>
  146. 146. Ajax $.ajax({ async:true, beforeSend:function(xhr) {xhr.setRequestHeader('Accept', 'text/xml, */*')}, type: “get”, url: “/users”, dataType: “xml”, success: function(xml){ // js blah code // js blah code Browser // js blah code }); XML data DOM <data title=”title”> Ajax format.xml <item>1</item> <item>2</item> <item>3</item> </data> Server
  147. 147. Ajax $.ajax({ async:true, beforeSend:function(xhr) {xhr.setRequestHeader('Accept', 'text/json, */*')}, type: “get”, url: “/users”, dataType: “json”, success: function(json){ // js blah code // js blah code Browser // js blah code }); json data DOM [{"name": "aaaa", "updated_at": "2008/01/19 09:55:32 +0800", "id": 2, Ajax format.json "created_at": "2008/01/19 09:55:32 +0800"}, {"name": "bbbb222", "updated_at": "2008/01/19 09:56:11 +0800", "id": 3, "created_at": "2008/01/19 09:55:40 +0800"}] Server
  148. 148. RESTful 7 actions ?
  149. 149. action event has many attendees
  150. 150. Model design class Event < ActiveRecord::Base has_many :attendees end class Attendee < ActiveRecord::Base belongs_to :event end
  151. 151. nested resources(1) controller attendees map.resources :events do |event| event.resources :attendees, :controller => 'event_attendees' end
  152. 152. nested resources(1) controller attendees map.resources :events do |event| event.resources :attendees, :controller => 'event_attendees' end <%= link_to ‘event attendees’, event_attendees_path(@event) %>
  153. 153. nested resources(1) controller attendees map.resources :events do |event| event.resources :attendees, :controller => 'event_attendees' end <%= link_to ‘event attendees’, event_attendees_path(@event) %>
  154. 154. nested resources(1) controller attendees map.resources :events do |event| event.resources :attendees, :controller => 'event_attendees' end /events/2/attendees <%= link_to ‘event attendees’, event_attendees_path(@event) %>
  155. 155. nested resources(1) controller attendees map.resources :events do |event| event.resources :attendees, :controller => 'event_attendees' end /events/2/attendees <%= link_to ‘event attendees’, event_attendees_path(@event) %> class EventAttendeesController < ApplicationController def index @attendees = Event.find(params[:event_id]).attendees end ... end
  156. 156. nested resources(2) <%= link_to ‘show’, event_attendees_path(@event,@attendee) %>
  157. 157. nested resources(2) /events/2/attendees/3 <%= link_to ‘show’, event_attendees_path(@event,@attendee) %>
  158. 158. nested resources(2) /events/2/attendees/3 <%= link_to ‘show’, event_attendees_path(@event,@attendee) %> class EventAttendeesController < ApplicationController before_filter :find_event def show @attendees = @event.attendees.find(params[:id]) end protected def find_event @event = Event.find(params[:event_id]) end end
  159. 159. nested resources(2) /events/2/attendees/3 <%= link_to ‘show’, event_attendees_path(@event,@attendee) %> class EventAttendeesController < ApplicationController before_filter :find_event def show @attendees = @event.attendees.find(params[:id]) end Q: ?? protected Attendee.find(params[:id]) def find_event @event = Event.find(params[:event_id]) end end
  160. 160. nested resources(2) /events/2/attendees/3 <%= link_to ‘show’, event_attendees_path(@event,@attendee) %> class EventAttendeesController < ApplicationController before_filter :find_event def show @attendees = @event.attendees.find(params[:id]) end Q: ?? protected Attendee.find(params[:id]) def find_event @event = Event.find(params[:event_id]) Ans: end Scope Access end
  161. 161. Deep Nesting? Resources should never be nested more than one level deep.
  162. 162. action event memberships
  163. 163. Model design class Event < ActiveRecord::Base has_many :memberships has_many :users, :through => :memberships end class User < ActiveRecord::Base has_many :memberships has_many :events, :through => :memberships end class Membership < ActiveRecord::Base belongs_to :event belongs_to :user end
  164. 164. RESTful design 1 map.resources :memberships
  165. 165. RESTful design 1 map.resources :memberships class MembershipsController < ApplicationController # POST /memberships?group_id=2&user_id=1 def create end # DELETE /memberships/3 def destroy end end
  166. 166. RESTful design 2 map.resources :groups do |group| group.resources :memberships end
  167. 167. RESTful design 2 map.resources :groups do |group| group.resources :memberships end class MembershipsController < ApplicationController # POST /group/2/memberships/?user_id=1 def create end # DELETE /group/2/memberships/3 def destroy end end
  168. 168. action event has one map
  169. 169. singular resource route • resources map.resources :events • resource map.resources :events do |event| event.resource :map, :controller => ‘event_maps’ end
  170. 170. singular resource route • resources map.resources :events • resource map.resources :events do |event| event.resource :map, :controller => ‘event_maps’ end RESTful controller
  171. 171. singular resource route (cont.) • URL helper • index action • show, edit update URL Helper id <%= link_to ‘Login’, event_map_path(@event) %> <% form_for :event_map, :url => event_map_path(@event) do |f| %>
  172. 172. action operate event state (open/closed)
  173. 173. map.resources :events do |event| event.resource :closure, :controller => 'event_closures' end
  174. 174. map.resources :events do |event| event.resource :closure, :controller => 'event_closures' end class EventClosuresController < ApplicationController # POST /events/3/closure def create Event.find(params[:event_id]).close! end # DELETE /events/3/closure def destroy Event.find(params[:event_id]).open! end end
  175. 175. map.resources :events do |event| event.resource :closure, :controller => 'event_closures' end class EventClosuresController < ApplicationController # POST /events/3/closure def create Event.find(params[:event_id]).close! end # DELETE /events/3/closure def destroy Event.find(params[:event_id]).open! end end <%= link_to ‘close’, event_closure_path(@event), :method => :post %>
  176. 176. map.resources :events do |event| event.resource :closure, :controller => 'event_closures' end class EventClosuresController < ApplicationController # POST /events/3/closure def create Event.find(params[:event_id]).close! end # DELETE /events/3/closure def destroy Event.find(params[:event_id]).open! end end <%= link_to ‘close’, event_closure_path(@event), :method => :post %> <%= link_to ‘open’, event_closure_path(@event), :method => :delete %>
  177. 177. why not PUT closed=1 to /events/2 Text
  178. 178. why not PUT closed=1 to /events/2 Text “a separate resource” or “an attribute of event”
  179. 179. action search event
  180. 180. Extra Collection Routes map.resources :events, :collection => { :search => :get }
  181. 181. Extra Collection Routes map.resources :events, :collection => { :search => :get } class EventsController < ApplicationController def search @events = Event.find_by_keyword(params[:keyword]) end end
  182. 182. Extra Collection Routes map.resources :events, :collection => { :search => :get } class EventsController < ApplicationController def search @events = Event.find_by_keyword(params[:keyword]) end end <%= link_to ‘search’, search_events_path, :keyword => ‘osdc’ %>
  183. 183. action a event dashboard
  184. 184. Extra Member Routes map.resources :events, :member => { :dashboard => :get }
  185. 185. Extra Member Routes map.resources :events, :member => { :dashboard => :get } class EventsController < ApplicationController def dashboard @event = Event.find(params[:id]) end end
  186. 186. Extra Member Routes map.resources :events, :member => { :dashboard => :get } class EventsController < ApplicationController def dashboard @event = Event.find(params[:id]) end end <%= link_to ‘dashboard’, dashboard_event_path(event) %>
  187. 187. Route Customizations is not RESTful ??
  188. 188. Route Customizations is not RESTful ?? • you can think of it as a sub-resource of events resource. (and the sub-resource has only one action)
  189. 189. Route Customizations is not RESTful ?? • you can think of it as a sub-resource of events resource. (and the sub-resource has only one action) • If you have too many extra routes, you should consider another resources.
  190. 190. action sorting event
  191. 191. Use query variables • Need not new resource def index sort_by = (params[:order] == ‘name’) ? ‘name’ : ‘created_at’ @events = Event.find(:all, :order => sort_by) end
  192. 192. Use query variables • Need not new resource def index sort_by = (params[:order] == ‘name’) ? ‘name’ : ‘created_at’ @events = Event.find(:all, :order => sort_by) end <%= link_to ‘search’, events_path, :order => ‘name’ %>
  193. 193. action event admin
  194. 194. namespace map.namespace :admin do |admin| admin.resources :events end
  195. 195. namespace map.namespace :admin do |admin| admin.resources :events end # /app/controllers/admin/events_controller.rb class Admin::EventsController < ApplicationController before_filter :require_admin def index .... end end
  196. 196. Considerations(1)
  197. 197. Considerations(1) • a REST resource does not map directly to model. It’s high-level abstractions of what’s available through your web app. (Not always 1-to-1, maybe 1-to-many or 1-to-zero)
  198. 198. Considerations(1) • a REST resource does not map directly to model. It’s high-level abstractions of what’s available through your web app. (Not always 1-to-1, maybe 1-to-many or 1-to-zero) • You don’t need to use all 7 actions if you don’t need them.
  199. 199. map.resource :session # This controller handles the login/logout function of the site. class SessionsController < ApplicationController def create self.current_user = User.authenticate(params[:login], params[:password]) if logged_in? redirect_back_or_default('/') else render :action => 'new' end end def destroy self.current_user.forget_me if logged_in? cookies.delete :auth_token reset_session redirect_back_or_default('/') end end
  200. 200. map.resource :session # This controller handles the login/logout function of the site. class SessionsController < ApplicationController def create self.current_user = User.authenticate(params[:login], params[:password]) if logged_in? redirect_back_or_default('/') else render :action => 'new' end end def destroy self.current_user.forget_me if logged_in? cookies.delete :auth_token reset_session redirect_back_or_default('/') end end => session
  201. 201. Considerations(2) • a RESTful controller may represent the creation or delete of only a concept. For example, a SpamsController create spam by changing a comment’s status to spam without adding any records to the DB.
  202. 202. Considerations(3)
  203. 203. Considerations(3) • one resources should be associated with one controller. (well, you can use one controller handle more than one resources)
  204. 204. Considerations(3) • one resources should be associated with one controller. (well, you can use one controller handle more than one resources) • offload privileged views into either a different controller or action.
  205. 205. 1 attendee Model 2 Resources related (2 Controller)
  206. 206. map.resources :attendees 1 attendee Model 2 Resources related (2 Controller)
  207. 207. map.resources :attendees 1 attendee Model 2 Resources related (2 Controller)
  208. 208. map.resources :attendees class AttendeeController < ApplicationController before_filter :manager_required def show @person = @event.attendees.find(params[:id]) end end 1 attendee Model 2 Resources related (2 Controller)
  209. 209. for event manager map.resources :attendees class AttendeeController < ApplicationController before_filter :manager_required def show @person = @event.attendees.find(params[:id]) end end 1 attendee Model 2 Resources related (2 Controller)
  210. 210. for event manager map.resources :attendees class AttendeeController < ApplicationController before_filter :manager_required def show @person = @event.attendees.find(params[:id]) end end map.resources :registers 1 attendee Model 2 Resources related (2 Controller)
  211. 211. for event manager map.resources :attendees class AttendeeController < ApplicationController before_filter :manager_required def show @person = @event.attendees.find(params[:id]) end end map.resources :registers 1 attendee Model 2 Resources related (2 Controller)
  212. 212. for event manager map.resources :attendees class AttendeeController < ApplicationController before_filter :manager_required def show @person = @event.attendees.find(params[:id]) end end map.resources :registers class RegistersController < ApplicationController 1 attendee Model before_filter :login_required 2 Resources related def show @person = current_user.registers.find(params[:id]) (2 Controller) end end
  213. 213. for event manager map.resources :attendees class AttendeeController < ApplicationController before_filter :manager_required def show @person = @event.attendees.find(params[:id]) end end map.resources :registers for attendeeing user class RegistersController < ApplicationController 1 attendee Model before_filter :login_required 2 Resources related def show @person = current_user.registers.find(params[:id]) (2 Controller) end end
  214. 214. [namespace]_[custom route]_[parent resource]_event[s]_path( [p,] e)
  215. 215. new, edit ? [namespace]_[custom route]_[parent resource]_event[s]_path( [p,] e)
  216. 216. new, edit nested ? [namespace]_[custom route]_[parent resource]_event[s]_path( [p,] e)
  217. 217. new, edit nested ? ? ? [namespace]_[custom route]_[parent resource]_event[s]_path( [p,] e)
  218. 218. new, edit nested ? ? ? [namespace]_[custom route]_[parent resource]_event[s]_path( [p,] e) :method => GET | POST | PUT | DELETE
  219. 219. so, what’s REST style?
  220. 220. Nouns
  221. 221. Nouns URL
  222. 222. Nouns URL
  223. 223. Nouns URL Verb
  224. 224. Nouns URL Verb finite HTTP methods
  225. 225. URL without Nouns action URL Verb finite HTTP methods
  226. 226. URL without Nouns action URL Verb finite HTTP methods
  227. 227. URL without Nouns action URL Verb Content Types finite HTTP methods
  228. 228. URL without Nouns action URL Verb Content Types finite HTTP methods HTML, XML, JSON....etc
  229. 229. URL without Nouns the resource have action URL many representations Verb Content Types finite HTTP methods HTML, XML, JSON....etc
  230. 230. URL without Nouns the resource have action URL many representations Verb Content Types finite HTTP methods HTML, XML, JSON....etc
  231. 231. REST end.
  232. 232. Reference books: • Rails Way(Addison Wesley) • Advanced Rails (Pragmatic) • Code Review PDF (peepcode.com) • Rails2 PDF (peepcode.com) • RESTful Web Services (O’REILLY)

×