Ruby on Rails Part3: RESTful & Ajax ihower@gmail.com http://creativecommons.org/licenses/by-nc/2.5/tw/
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
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
Representational State Transfer ( REST) • Roy Fielding 2000 • SOAP XML-RPC • Web Service API Amazon Yahoo! Google API
Rails RESTful ?
RESTful : designing controller and action is chaos
controller class EventsController < ApplicationController index show new create edit update destroy
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.
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.
named routes # routes.rb map.connect '/:controller/:action/:id' <%= link_to ‘text’, :controller => ‘events’, :action => ‘show’, :id => event.id %>
named routes # routes.rb map.connect '/:controller/:action/:id' <%= link_to ‘text’, :controller => ‘events’, :action => ‘show’, :id => event.id %> named route
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'
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) %>
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) %>
: controllers actions!!
The ideas from CRUD...
HTTP methods (RFC 2616) POST GET PUT DELETE Create Read Update Delete
HTTP methods (RFC 2616) POST GET PUT DELETE Create Read Update Delete GET is defined as a safe method
/events/create /events/show/1 /events/update/1 /events/destroy/1
/events/create Add HTTP method /events/show/1 /events/update/1 /events/destroy/1
/events/create Add HTTP method /events/show/1 /events/update/1 /events/destroy/1
/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
/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.
CRUD-based action names get things simpler create show update delete POST GET PUT DELETE
CRUD-based action names get things simpler create show update delete POST GET PUT DELETE controller CRUD
routes.rb ActionController::Routing::Routes.draw do |map| map.resources :events end named routes actions
routes.rb ActionController::Routing::Routes.draw do |map| map.resources :events end a resource is something with URL named routes actions
routes.rb ActionController::Routing::Routes.draw do |map| map.resources :events end a resource is something with URL named routes actions 4
routes.rb ActionController::Routing::Routes.draw do |map| map.resources :events end a resource is something with URL named routes actions 4 7
routes.rb ActionController::Routing::Routes.draw do |map| map.resources :events end a resource is something with URL named routes actions 4 7
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
RESTful CRUD
index The default request method is GET <%= link_to ‘event list’, events_path %> class EventsController < ApplicationController def index @events = Event.find(:all) end ... end
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
new/create <%= link_to ‘new event’, new_event_path %> class EventsController < ApplicationController def new @event = Event.new end end
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 %>
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 %>
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 %>
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
edit/update <%= link_to event.name, edit_event_path(event) %> class EventsController < ApplicationController def edit @event = Event.find(params[:id]) end end
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 %>
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 %>
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
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
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
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
Singular and Plural RESTful Routes • show, new, edit, destroy • index, new, create
Singular and Plural RESTful Routes • show, new, edit, destroy • index, new, create event_path(@event)
Singular and Plural RESTful Routes • show, new, edit, destroy • index, new, create event_path(@event) HTTP verb show, update, destroy
Singular and Plural RESTful Routes • show, new, edit, destroy • index, new, create event_path(@event) HTTP verb show, update, destroy events_path
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
[custom route]_event[s]_path( event )
new, edit [custom route]_event[s]_path( event )
? new, edit ? [custom route]_event[s]_path( event )
_path ? _url http://domain/ new, edit ? [custom route]_event[s]_path( event )
_path ? _url http://domain/ new, edit ? [custom route]_event[s]_path( event ) :method => GET | POST | PUT | DELETE
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id' link_to event.name, :controller => ‘events’, :action => :show , :id => event.id
map.connect ':controller/:action/:id' link_to event.name, :controller => ‘events’, :action => :show , :id => event.id
map.connect ':controller/:action/:id' link_to event.name, :controller => ‘events’, :action => :show , :id => event.id
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
PUT? DELETE?
The PUT&DELETE Cheat
The PUT&DELETE Cheat • PUT&DELETE method
The PUT&DELETE Cheat • PUT&DELETE method • Rails _method
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>
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>
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>
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>
The Problem
The Problem • HTML GET/POST HTML forms PUT/DELETE
The Problem • HTML GET/POST HTML forms PUT/DELETE • XmlHttpRequest ( Ajax request) GET/POST/PUT/DELETE/HEAD/OPTIONS
The Problem • HTML GET/POST HTML forms PUT/DELETE • XmlHttpRequest ( Ajax request) GET/POST/PUT/DELETE/HEAD/OPTIONS • Firefox/Safari
The Problem • HTML GET/POST HTML forms PUT/DELETE • XmlHttpRequest ( Ajax request) GET/POST/PUT/DELETE/HEAD/OPTIONS • Firefox/Safari • Opera PUT/DELETE
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 !
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.)
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.
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 ?
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
RESTful Scaffold
respond_to ?
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
format.html
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.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
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>
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
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
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
action Don’t repeat yourself
formats • format.html • format.csv • format.xml • format.xls • format.js • format.yaml • format.json • format.txt • format.atom • more.... • format.rss
http://registrano.com/events/5381ae/attendees
http://registrano.com/events/5381ae/attendees
http://registrano.com/events/5381ae/attendees http://registrano.com/events/5381ae/attendees.csv
http://registrano.com/events/5381ae/attendees http://registrano.com/events/5381ae/attendees.csv http://registrano.com/events/5381ae/attendees.xls
( UI )
XML API JSON API ( UI )
XML API JSON API ( UI ) Adobe Flex
Rails: how to know?
URL http://localhost:3000/users.xml template <%= link_to ‘User List’, formatted_users_path(:xml) %> <a href=”/users.xml”>User List</a>
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
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
• params[:format] GET /users/1?format=xml
• 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
custom format # config/initializers/mime_types.rb Mime::Type.register ‘audio/mpeg’, :mp3?Mime::Type.register ‘audio/mpegurl’, :m3u
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
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
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
template ?
template • format (minetype) template generator (renderer) • Rails2 action.minetype.renderer filename.html.erb
template def index @users = User.find(:all) respond_to do |format| format.html # index.html.erb format.xml # index.xml.builder end end
template def index @users = User.find(:all) respond_to do |format| format.html # index.html.erb format.xml # index.xml.builder end end
erb template • ruby code • HTML ( format.html) <h1><%= @event.name %></h1>
erb template • ruby code • HTML ( format.html) <h1><%= @event.name %></h1>
erb template • ruby code • HTML ( format.html) <h1><%= @event.name %></h1> <h1>OSDC 2008</h1>
erb template • ruby code • HTML ( format.html) <h1><%= @event.name %></h1> show.html.erb <h1>OSDC 2008</h1>
builder template • Ruby XML xml.instruct! xml.title "This is a title" xml.person do xml.first_name "Ryan" xml.last_name "Raaum" end
builder template • Ruby XML xml.instruct! xml.title "This is a title" xml.person do xml.first_name "Ryan" xml.last_name "Raaum" end
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>
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>
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
Ajax on Rails
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
Ajax (1)
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
RJS template <%= link_to_remote ‘ajax show’, :url => event_path(@event) %>
RJS template <%= link_to_remote ‘ajax show’, :url => event_path(@event) %>
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
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
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’
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’
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’
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 }
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 }
Ajax (2)
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
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
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
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();
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;”>
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;”>
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;”>
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;”>
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;”>
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
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
RESTful 7 actions ?
action event has many attendees
Model design class Event < ActiveRecord::Base has_many :attendees end class Attendee < ActiveRecord::Base belongs_to :event end
nested resources(1) controller attendees map.resources :events do |event| event.resources :attendees, :controller => 'event_attendees' end
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) %>
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) %>
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) %>
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
nested resources(2) <%= link_to ‘show’, event_attendees_path(@event,@attendee) %>
nested resources(2) /events/2/attendees/3 <%= link_to ‘show’, event_attendees_path(@event,@attendee) %>
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
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
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
Deep Nesting? Resources should never be nested more than one level deep.
action event memberships
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
RESTful design 1 map.resources :memberships
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
RESTful design 2 map.resources :groups do |group| group.resources :memberships end
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
action event has one map
singular resource route • resources map.resources :events • resource map.resources :events do |event| event.resource :map, :controller => ‘event_maps’ end
singular resource route • resources map.resources :events • resource map.resources :events do |event| event.resource :map, :controller => ‘event_maps’ end RESTful controller
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| %>
action operate event state (open/closed)
map.resources :events do |event| event.resource :closure, :controller => 'event_closures' end
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
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 %>
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 %>
why not PUT closed=1 to /events/2 Text
why not PUT closed=1 to /events/2 Text “a separate resource” or “an attribute of event”
action search event
Extra Collection Routes map.resources :events, :collection => { :search => :get }
Extra Collection Routes map.resources :events, :collection => { :search => :get } class EventsController < ApplicationController def search @events = Event.find_by_keyword(params[:keyword]) end end
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’ %>
action a event dashboard
Extra Member Routes map.resources :events, :member => { :dashboard => :get }
Extra Member Routes map.resources :events, :member => { :dashboard => :get } class EventsController < ApplicationController def dashboard @event = Event.find(params[:id]) end end
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) %>
Route Customizations is not RESTful ??
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)
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.
action sorting event
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
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’ %>
action event admin
namespace map.namespace :admin do |admin| admin.resources :events end
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
Considerations(1)
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)
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.
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
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
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.
Considerations(3)
Considerations(3) • one resources should be associated with one controller. (well, you can use one controller handle more than one resources)
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.
1 attendee Model 2 Resources related (2 Controller)
map.resources :attendees 1 attendee Model 2 Resources related (2 Controller)
map.resources :attendees 1 attendee Model 2 Resources related (2 Controller)
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)
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)
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)
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)
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
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
[namespace]_[custom route]_[parent resource]_event[s]_path( [p,] e)
new, edit ? [namespace]_[custom route]_[parent resource]_event[s]_path( [p,] e)
new, edit nested ? [namespace]_[custom route]_[parent resource]_event[s]_path( [p,] e)
new, edit nested ? ? ? [namespace]_[custom route]_[parent resource]_event[s]_path( [p,] e)
new, edit nested ? ? ? [namespace]_[custom route]_[parent resource]_event[s]_path( [p,] e) :method => GET | POST | PUT | DELETE
so, what’s REST style?
Nouns
Nouns URL
Nouns URL
Nouns URL Verb
Nouns URL Verb finite HTTP methods
URL without Nouns action URL Verb finite HTTP methods
URL without Nouns action URL Verb finite HTTP methods
URL without Nouns action URL Verb Content Types finite HTTP methods
URL without Nouns action URL Verb Content Types finite HTTP methods HTML, XML, JSON....etc
URL without Nouns the resource have action URL many representations Verb Content Types finite HTTP methods HTML, XML, JSON....etc
URL without Nouns the resource have action URL many representations Verb Content Types finite HTTP methods HTML, XML, JSON....etc
REST end.
Reference books: • Rails Way(Addison Wesley) • Advanced Rails (Pragmatic) • Code Review PDF (peepcode.com) • Rails2 PDF (peepcode.com) • RESTful Web Services (O’REILLY)

Ruby on Rails : RESTful 和 Ajax

  • 1.
    Ruby on Rails Part3:RESTful & Ajax ihower@gmail.com http://creativecommons.org/licenses/by-nc/2.5/tw/
  • 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.
    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.
    Representational State Transfer ( REST) • Roy Fielding 2000 • SOAP XML-RPC • Web Service API Amazon Yahoo! Google API
  • 8.
  • 9.
    RESTful : designing controller and action is chaos
  • 10.
    controller class EventsController <ApplicationController index show new create edit update destroy
  • 11.
    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.
  • 12.
    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.
  • 13.
    named routes # routes.rb map.connect'/:controller/:action/:id' <%= link_to ‘text’, :controller => ‘events’, :action => ‘show’, :id => event.id %>
  • 14.
    named routes # routes.rb map.connect'/:controller/:action/:id' <%= link_to ‘text’, :controller => ‘events’, :action => ‘show’, :id => event.id %> named route
  • 15.
    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'
  • 16.
    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) %>
  • 17.
    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) %>
  • 18.
    : controllers actions!!
  • 19.
  • 20.
    HTTP methods (RFC 2616) POST GET PUT DELETE Create Read Update Delete
  • 21.
    HTTP methods (RFC 2616) POST GET PUT DELETE Create Read Update Delete GET is defined as a safe method
  • 22.
  • 23.
    /events/create Add HTTP method /events/show/1 /events/update/1 /events/destroy/1
  • 24.
    /events/create Add HTTP method /events/show/1 /events/update/1 /events/destroy/1
  • 25.
    /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
  • 26.
    /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.
  • 27.
    CRUD-based action names get things simpler create show update delete POST GET PUT DELETE
  • 28.
    CRUD-based action names get things simpler create show update delete POST GET PUT DELETE controller CRUD
  • 29.
    routes.rb ActionController::Routing::Routes.draw do |map| map.resources :events end named routes actions
  • 30.
    routes.rb ActionController::Routing::Routes.draw do |map| map.resources :events end a resource is something with URL named routes actions
  • 31.
    routes.rb ActionController::Routing::Routes.draw do |map| map.resources :events end a resource is something with URL named routes actions 4
  • 32.
    routes.rb ActionController::Routing::Routes.draw do |map| map.resources :events end a resource is something with URL named routes actions 4 7
  • 33.
    routes.rb ActionController::Routing::Routes.draw do |map| map.resources :events end a resource is something with URL named routes actions 4 7
  • 34.
    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
  • 35.
  • 36.
    index The default request method is GET <%= link_to ‘event list’, events_path %> class EventsController < ApplicationController def index @events = Event.find(:all) end ... end
  • 37.
    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
  • 38.
    new/create <%= link_to ‘newevent’, new_event_path %> class EventsController < ApplicationController def new @event = Event.new end end
  • 39.
    new/create <%= link_to ‘newevent’, 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 %>
  • 40.
    new/create <%= link_to ‘newevent’, 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 %>
  • 41.
    new/create <%= link_to ‘newevent’, 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 %>
  • 42.
    new/create <%= link_to ‘newevent’, 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
  • 43.
    edit/update <%= link_to event.name,edit_event_path(event) %> class EventsController < ApplicationController def edit @event = Event.find(params[:id]) end end
  • 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" %> <% end %>
  • 45.
    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 %>
  • 46.
    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
  • 47.
    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
  • 48.
    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
  • 49.
    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
  • 50.
    Singular and PluralRESTful Routes • show, new, edit, destroy • index, new, create
  • 51.
    Singular and PluralRESTful Routes • show, new, edit, destroy • index, new, create event_path(@event)
  • 52.
    Singular and PluralRESTful Routes • show, new, edit, destroy • index, new, create event_path(@event) HTTP verb show, update, destroy
  • 53.
    Singular and PluralRESTful Routes • show, new, edit, destroy • index, new, create event_path(@event) HTTP verb show, update, destroy events_path
  • 54.
    Singular and PluralRESTful Routes • show, new, edit, destroy • index, new, create event_path(@event) HTTP verb show, update, destroy events_path HTTP verb index, create
  • 55.
  • 56.
    new, edit [custom route]_event[s]_path( event )
  • 57.
    ? new, edit ? [custom route]_event[s]_path( event )
  • 58.
    _path ? _url http://domain/ new, edit ? [custom route]_event[s]_path( event )
  • 59.
    _path ? _url http://domain/ new, edit ? [custom route]_event[s]_path( event ) :method => GET | POST | PUT | DELETE
  • 60.
  • 61.
  • 62.
    map.connect ':controller/:action/:id' link_to event.name,:controller => ‘events’, :action => :show , :id => event.id
  • 63.
    map.connect ':controller/:action/:id' link_to event.name,:controller => ‘events’, :action => :show , :id => event.id
  • 64.
    map.connect ':controller/:action/:id' link_to event.name,:controller => ‘events’, :action => :show , :id => event.id
  • 65.
    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
  • 66.
  • 67.
  • 68.
    The PUT&DELETE Cheat • PUT&DELETE method
  • 69.
    The PUT&DELETE Cheat • PUT&DELETE method • Rails _method
  • 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>
  • 71.
    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>
  • 72.
    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>
  • 73.
    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>
  • 74.
  • 75.
    The Problem • HTML GET/POST HTML forms PUT/DELETE
  • 76.
    The Problem • HTML GET/POST HTML forms PUT/DELETE • XmlHttpRequest ( Ajax request) GET/POST/PUT/DELETE/HEAD/OPTIONS
  • 77.
    The Problem • HTML GET/POST HTML forms PUT/DELETE • XmlHttpRequest ( Ajax request) GET/POST/PUT/DELETE/HEAD/OPTIONS • Firefox/Safari
  • 78.
    The Problem • HTML GET/POST HTML forms PUT/DELETE • XmlHttpRequest ( Ajax request) GET/POST/PUT/DELETE/HEAD/OPTIONS • Firefox/Safari • Opera PUT/DELETE
  • 79.
    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 !
  • 80.
    As a RubyOn 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.)
  • 81.
    The type ofrequest 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.
  • 82.
    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 ?
  • 83.
    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
  • 84.
  • 85.
  • 86.
    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
  • 87.
  • 88.
    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>
  • 89.
    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
  • 90.
    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>
  • 91.
    you don't needthis! 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
  • 92.
    you don't needthis! 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
  • 93.
    you don't needthis! 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
  • 94.
  • 95.
    formats • format.html • format.csv • format.xml • format.xls • format.js • format.yaml • format.json • format.txt • format.atom • more.... • format.rss
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
    ( UI )
  • 101.
    XML API JSON API ( UI )
  • 102.
    XML API JSON API ( UI ) Adobe Flex
  • 103.
  • 104.
    URL http://localhost:3000/users.xml template <%= link_to ‘User List’, formatted_users_path(:xml) %> <a href=”/users.xml”>User List</a>
  • 105.
    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
  • 106.
    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
  • 107.
    params[:format] GET /users/1?format=xml
  • 108.
    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
  • 109.
    custom format # config/initializers/mime_types.rb Mime::Type.register‘audio/mpeg’, :mp3?Mime::Type.register ‘audio/mpegurl’, :m3u
  • 110.
    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
  • 111.
    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
  • 112.
    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
  • 113.
  • 114.
    template • format (minetype) template generator (renderer) • Rails2 action.minetype.renderer filename.html.erb
  • 115.
    template defindex @users = User.find(:all) respond_to do |format| format.html # index.html.erb format.xml # index.xml.builder end end
  • 116.
    template defindex @users = User.find(:all) respond_to do |format| format.html # index.html.erb format.xml # index.xml.builder end end
  • 117.
    erb template • ruby code • HTML ( format.html) <h1><%= @event.name %></h1>
  • 118.
    erb template • ruby code • HTML ( format.html) <h1><%= @event.name %></h1>
  • 119.
    erb template • ruby code • HTML ( format.html) <h1><%= @event.name %></h1> <h1>OSDC 2008</h1>
  • 120.
    erb template • ruby code • HTML ( format.html) <h1><%= @event.name %></h1> show.html.erb <h1>OSDC 2008</h1>
  • 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
  • 122.
    builder template • Ruby XML xml.instruct! xml.title "This is a title" xml.person do xml.first_name "Ryan" xml.last_name "Raaum" end
  • 123.
    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>
  • 124.
    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>
  • 125.
    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
  • 126.
  • 127.
    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
  • 128.
    Ajax (1)
  • 129.
    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
  • 130.
    RJS template <%= link_to_remote‘ajax show’, :url => event_path(@event) %>
  • 131.
    RJS template <%= link_to_remote‘ajax show’, :url => event_path(@event) %>
  • 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 end
  • 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
  • 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’
  • 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’
  • 136.
    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’
  • 137.
    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 }
  • 138.
    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 }
  • 139.
    Ajax (2)
  • 140.
    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
  • 141.
    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
  • 142.
    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
  • 143.
    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();
  • 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 <a href=”/users” onclick=”$.ajax(...blah...);return false;”>
  • 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 <a href=”/users” onclick=”$.ajax(...blah...);return false;”>
  • 146.
    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;”>
  • 147.
    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;”>
  • 148.
    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;”>
  • 149.
    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
  • 150.
    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
  • 151.
    RESTful 7 actions ?
  • 152.
  • 153.
    Model design class Event< ActiveRecord::Base has_many :attendees end class Attendee < ActiveRecord::Base belongs_to :event end
  • 154.
    nested resources(1) controller attendees map.resources :events do |event| event.resources :attendees, :controller => 'event_attendees' end
  • 155.
    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) %>
  • 156.
    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) %>
  • 157.
    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) %>
  • 158.
    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
  • 159.
    nested resources(2) <%= link_to‘show’, event_attendees_path(@event,@attendee) %>
  • 160.
    nested resources(2) /events/2/attendees/3 <%= link_to ‘show’, event_attendees_path(@event,@attendee) %>
  • 161.
    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
  • 162.
    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
  • 163.
    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
  • 164.
    Deep Nesting? Resources shouldnever be nested more than one level deep.
  • 165.
  • 166.
    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
  • 167.
  • 168.
    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
  • 169.
    RESTful design 2 map.resources:groups do |group| group.resources :memberships end
  • 170.
    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
  • 171.
  • 172.
    singular resource route • resources map.resources :events • resource map.resources :events do |event| event.resource :map, :controller => ‘event_maps’ end
  • 173.
    singular resource route • resources map.resources :events • resource map.resources :events do |event| event.resource :map, :controller => ‘event_maps’ end RESTful controller
  • 174.
    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| %>
  • 175.
  • 176.
    map.resources :events do|event| event.resource :closure, :controller => 'event_closures' end
  • 177.
    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
  • 178.
    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 %>
  • 179.
    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 %>
  • 180.
    why not PUT closed=1to /events/2 Text
  • 181.
    why not PUT closed=1to /events/2 Text “a separate resource” or “an attribute of event”
  • 182.
  • 183.
    Extra Collection Routes map.resources :events, :collection => { :search => :get }
  • 184.
    Extra Collection Routes map.resources :events, :collection => { :search => :get } class EventsController < ApplicationController def search @events = Event.find_by_keyword(params[:keyword]) end end
  • 185.
    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’ %>
  • 186.
  • 187.
    Extra Member Routes map.resources:events, :member => { :dashboard => :get }
  • 188.
    Extra Member Routes map.resources :events, :member => { :dashboard => :get } class EventsController < ApplicationController def dashboard @event = Event.find(params[:id]) end end
  • 189.
    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) %>
  • 190.
    Route Customizations is not RESTful ??
  • 191.
    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)
  • 192.
    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.
  • 193.
  • 194.
    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
  • 195.
    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’ %>
  • 196.
  • 197.
    namespace map.namespace :admin do|admin| admin.resources :events end
  • 198.
    namespace map.namespace :admindo |admin| admin.resources :events end # /app/controllers/admin/events_controller.rb class Admin::EventsController < ApplicationController before_filter :require_admin def index .... end end
  • 199.
  • 200.
    Considerations(1) • a RESTresource 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)
  • 201.
    Considerations(1) • a RESTresource 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.
  • 202.
    map.resource :session # Thiscontroller 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
  • 203.
    map.resource :session # Thiscontroller 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
  • 204.
    Considerations(2) • a RESTfulcontroller 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.
  • 205.
  • 206.
    Considerations(3) • one resourcesshould be associated with one controller. (well, you can use one controller handle more than one resources)
  • 207.
    Considerations(3) • one resourcesshould 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.
  • 208.
    1 attendee Model 2Resources related (2 Controller)
  • 209.
    map.resources :attendees 1 attendee Model 2 Resources related (2 Controller)
  • 210.
    map.resources :attendees 1 attendee Model 2 Resources related (2 Controller)
  • 211.
    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)
  • 212.
    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)
  • 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 1 attendee Model 2 Resources related (2 Controller)
  • 214.
    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)
  • 215.
    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
  • 216.
    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
  • 217.
  • 218.
    new, edit ? [namespace]_[custom route]_[parent resource]_event[s]_path( [p,] e)
  • 219.
    new, edit nested ? [namespace]_[custom route]_[parent resource]_event[s]_path( [p,] e)
  • 220.
    new, edit nested ? ? ? [namespace]_[custom route]_[parent resource]_event[s]_path( [p,] e)
  • 221.
    new, edit nested ? ? ? [namespace]_[custom route]_[parent resource]_event[s]_path( [p,] e) :method => GET | POST | PUT | DELETE
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
    Nouns URL Verb
  • 227.
    Nouns URL Verb finite HTTP methods
  • 228.
    URL without Nouns action URL Verb finite HTTP methods
  • 229.
    URL without Nouns action URL Verb finite HTTP methods
  • 230.
    URL without Nouns action URL Verb Content Types finite HTTP methods
  • 231.
    URL without Nouns action URL Verb Content Types finite HTTP methods HTML, XML, JSON....etc
  • 232.
    URL without Nouns the resource have action URL many representations Verb Content Types finite HTTP methods HTML, XML, JSON....etc
  • 233.
    URL without Nouns the resource have action URL many representations Verb Content Types finite HTTP methods HTML, XML, JSON....etc
  • 234.
  • 235.
    Reference books: • RailsWay(Addison Wesley) • Advanced Rails (Pragmatic) • Code Review PDF (peepcode.com) • Rails2 PDF (peepcode.com) • RESTful Web Services (O’REILLY)