KEMBAR78
Ruby on Rails : RESTful 和 Ajax | PDF
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)