提交 f59984cc 编写于 作者: J José Valim

Add nagivational behavior to respond_with.

上级 c44f7e39
......@@ -55,14 +55,15 @@ def head(*args)
elsif args.empty?
raise ArgumentError, "too few arguments to head"
end
options = args.extract_options!
status = args.shift || options.delete(:status) || :ok
options = args.extract_options!
status = args.shift || options.delete(:status) || :ok
location = options.delete(:location)
options.each do |key, value|
headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s
end
render :nothing => true, :status => status
render :nothing => true, :status => status, :location => location
end
# Sets the etag and/or last_modified on the response and checks it against
......
......@@ -198,11 +198,11 @@ def respond_to(*mimes, &block)
end
# respond_with allows you to respond an action with a given resource. It
# requires that you set your class with a :respond_to method with the
# requires that you set your class with a respond_to method with the
# formats allowed:
#
# class PeopleController < ApplicationController
# respond_to :xml, :json
# respond_to :html, :xml, :json
#
# def index
# @people = Person.find(:all)
......@@ -210,14 +210,43 @@ def respond_to(*mimes, &block)
# end
# end
#
# When a request comes with format :xml, the respond_with will first search
# for a template as person/index.xml, if the template is not available, it
# will see if the given resource responds to :to_xml.
# When a request comes, for example with format :xml, three steps happen:
#
# 1) respond_with searches for a template at people/index.xml;
#
# 2) if the template is not available, it will check if the given
# resource responds to :to_xml.
#
# 3) if a :location option was provided, redirect to the location with
# redirect status if a string was given, or render an action if a
# symbol was given.
#
# If all steps fail, a missing template error will be raised.
#
# === Supported options
#
# [status]
# Sets the response status.
#
# [head]
# Tell respond_with to set the content type, status and location header,
# but do not render the object, leaving the response body empty. This
# option only has effect if the resource is being rendered. If a
# template was found, it's going to be rendered anyway.
#
# [location]
# Sets the location header with the given value. It accepts a string,
# representing the location header value, or a symbol representing an
# action name.
#
# === Builtin HTTP verb semantics
#
# If neither are available, it will raise an error.
# respond_with holds semantics for each HTTP verb. Depending on the verb
# and the resource status, respond_with will automatically set the options
# above.
#
# respond_with holds semantics for each HTTP verb. The example above cover
# GET requests. Let's check a POST request example:
# Above we saw an example for GET requests, where actually no option is
# configured. A create action for POST requests, could be written as:
#
# def create
# @person = Person.new(params[:person])
......@@ -225,34 +254,40 @@ def respond_to(*mimes, &block)
# respond_with(@person)
# end
#
# Since the request is a POST, respond_with will check wether @people
# resource have errors or not. If it has errors, it will render the error
# object with unprocessable entity status (422).
# respond_with will inspect the @person object and check if we have any
# error. If errors are empty, it will add status and location to the options
# hash. Then the create action in case of success, is equivalent to this:
#
# If no error was found, it will render the @people resource, with status
# created (201) and location header set to person_url(@people).
# respond_with(@person, :status => :created, :location => @person)
#
# If you also want to provide html behavior in the method above, you can
# supply a block to customize it:
# From them on, the lookup happens as described above. Let's suppose a :xml
# request and we don't have a people/create.xml template. But since the
# @person object responds to :to_xml, it will render the newly created
# resource and set status and location.
#
# class PeopleController < ApplicationController
# respond_to :html, :xml, :json # Add :html to respond_to definition
#
# def create
# @person = Person.new(params[:pe])
#
# respond_with(@person) do |format|
# if @person.save
# flash[:notice] = 'Person was successfully created.'
# format.html { redirect_to @person }
# else
# format.html { render :action => "new" }
# end
# end
# However, if the request is :html, a template is not available and @person
# does not respond to :to_html. But since a :location options was provided,
# it will redirect to it.
#
# In case of failures (when the @person could not be saved and errors are
# not empty), respond_with can be expanded as this:
#
# respond_with(@person.errors, :status => :unprocessable_entity, :location => :new)
#
# In other words, respond_with(@person) for POST requests is expanded
# internally into this:
#
# def create
# @person = Person.new(params[:person])
#
# if @person.save
# respond_with(@person, :status => :created, :location => @person)
# else
# respond_with(@person.errors, :status => :unprocessable_entity, :location => :new)
# end
# end
#
# It works similarly for PUT requests:
# For an update action for PUT requests, we would have:
#
# def update
# @person = Person.find(params[:id])
......@@ -260,10 +295,23 @@ def respond_to(*mimes, &block)
# respond_with(@person)
# end
#
# In case of failures, it works as POST requests, but in success failures
# it just reply status ok (200) to the client.
# Which, in face of success and failure scenarios, can be expanded as:
#
# A DELETE request also works in the same way:
# def update
# @person = Person.find(params[:id])
# @person.update_attributes(params[:person])
#
# if @person.save
# respond_with(@person, :status => :ok, :location => @person, :head => true)
# else
# respond_with(@person.errors, :status => :unprocessable_entity, :location => :edit)
# end
# end
#
# Notice that in case of success, we just need to reply :ok to the client.
# The option :head ensures that the object is not rendered.
#
# Finally, we have the destroy action with DELETE verb:
#
# def destroy
# @person = Person.find(params[:id])
......@@ -271,21 +319,30 @@ def respond_to(*mimes, &block)
# respond_with(@person)
# end
#
# It just replies with status ok, indicating the record was successfuly
# destroyed.
# Which is expanded as:
#
# def destroy
# @person = Person.find(params[:id])
# @person.destroy
# respond_with(@person, :status => :ok, :location => @person, :head => true)
# end
#
# In this case, since @person.destroyed? returns true, polymorphic urls will
# redirect to the collection url, instead of the resource url.
#
def respond_with(resource, options={}, &block)
respond_to(&block)
rescue ActionView::MissingTemplate => e
format = self.formats.first
resource = normalize_resource_options_by_verb(resource, options)
action = options.delete(:location) if options[:location].is_a?(Symbol)
if resource.respond_to?(:"to_#{format}")
if options.delete(:no_content)
head options
else
render options.merge(format => resource)
end
options.delete(:head) ? head(options) : render(options.merge(format => resource))
elsif action
render :action => action
elsif options[:location]
redirect_to options[:location]
else
raise e
end
......@@ -296,16 +353,22 @@ def respond_with(resource, options={}, &block)
# Change respond with behavior based on the HTTP verb.
#
def normalize_resource_options_by_verb(resource_or_array, options)
resource = resource_or_array.is_a?(Array) ? resource_or_array.last : resource_or_array
has_errors = resource.respond_to?(:errors) && !resource.errors.empty?
resource = resource_or_array.is_a?(Array) ? resource_or_array.last : resource_or_array
if has_errors && (request.post? || request.put?)
options.reverse_merge!(:status => :unprocessable_entity)
if resource.respond_to?(:errors) && !resource.errors.empty?
options[:status] ||= :unprocessable_entity
options[:location] ||= :new if request.post?
options[:location] ||= :edit if request.put?
return resource.errors
elsif request.post?
options.reverse_merge!(:status => :created, :location => resource_or_array)
elsif !request.get?
options.reverse_merge!(:status => :ok, :no_content => true)
options[:location] ||= resource_or_array
if request.post?
options[:status] ||= :created
else
options[:status] ||= :ok
options[:head] = true unless options.key?(:head)
end
end
return resource
......
......@@ -86,17 +86,16 @@ def polymorphic_url(record_or_hash_or_array, options = {})
else [ record_or_hash_or_array ]
end
inflection =
case
when options[:action].to_s == "new"
args.pop
:singular
when record.respond_to?(:new_record?) && record.new_record?
args.pop
:plural
else
:singular
end
inflection = if options[:action].to_s == "new"
args.pop
:singular
elsif (record.respond_to?(:new_record?) && record.new_record?) ||
(record.respond_to?(:destroyed?) && record.destroyed?)
args.pop
:plural
else
:singular
end
args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
......
......@@ -493,6 +493,10 @@ def to_js
def errors
[]
end
def destroyed?
false
end
end
class ParentResource
......@@ -508,7 +512,7 @@ def to_param
class RespondWithController < ActionController::Base
respond_to :html, :json
respond_to :xml, :except => :using_defaults
respond_to :js, :only => :using_defaults
respond_to :js, :only => [ :using_defaults, :using_resource ]
def using_defaults
respond_to do |format|
......@@ -547,12 +551,16 @@ def _render_js(js, options)
self.response_body = js.respond_to?(:to_js) ? js.to_js : js
end
def resources_url
request.host + "/resources"
end
def resource_url(resource)
request.host + "/resource/#{resource.to_param}"
request.host + "/resources/#{resource.to_param}"
end
def parent_resource_url(parent, resource)
request.host + "/parent/#{parent.to_param}/resource/#{resource.to_param}"
request.host + "/parents/#{parent.to_param}/resources/#{resource.to_param}"
end
end
......@@ -617,10 +625,10 @@ def test_default_overwritten
end
def test_using_resource
@request.accept = "text/html"
@request.accept = "text/javascript"
get :using_resource
assert_equal "text/html", @response.content_type
assert_equal "Hello world!", @response.body
assert_equal "text/javascript", @response.content_type
assert_equal '$("body").visualEffect("highlight");', @response.body
@request.accept = "application/xml"
get :using_resource
......@@ -633,14 +641,30 @@ def test_using_resource
end
end
def test_using_resource_for_post
def test_using_resource_for_post_with_html
post :using_resource
assert_equal "text/html", @response.content_type
assert_equal 302, @response.status
assert_equal "www.example.com/resources/13", @response.location
assert @response.redirect?
errors = { :name => :invalid }
RespondResource.any_instance.stubs(:errors).returns(errors)
post :using_resource
assert_equal "text/html", @response.content_type
assert_equal 200, @response.status
assert_equal "New world!\n", @response.body
assert_nil @response.location
end
def test_using_resource_for_post_with_xml
@request.accept = "application/xml"
post :using_resource
assert_equal "application/xml", @response.content_type
assert_equal 201, @response.status
assert_equal "XML", @response.body
assert_equal "www.example.com/resource/13", @response.location
assert_equal "www.example.com/resources/13", @response.location
errors = { :name => :invalid }
RespondResource.any_instance.stubs(:errors).returns(errors)
......@@ -651,14 +675,30 @@ def test_using_resource_for_post
assert_nil @response.location
end
def test_using_resource_for_put
def test_using_resource_for_put_with_html
put :using_resource
assert_equal "text/html", @response.content_type
assert_equal 302, @response.status
assert_equal "www.example.com/resources/13", @response.location
assert @response.redirect?
errors = { :name => :invalid }
RespondResource.any_instance.stubs(:errors).returns(errors)
put :using_resource
assert_equal "text/html", @response.content_type
assert_equal 200, @response.status
assert_equal "Edit world!\n", @response.body
assert_nil @response.location
end
def test_using_resource_for_put_with_xml
@request.accept = "application/xml"
put :using_resource
assert_equal "application/xml", @response.content_type
assert_equal 200, @response.status
assert_equal " ", @response.body
assert_nil @response.location
assert_equal "www.example.com/resources/13", @response.location
errors = { :name => :invalid }
RespondResource.any_instance.stubs(:errors).returns(errors)
......@@ -666,16 +706,25 @@ def test_using_resource_for_put
assert_equal "application/xml", @response.content_type
assert_equal 422, @response.status
assert_equal errors.to_xml, @response.body
assert_nil @response.location
assert_nil @response.location
end
def test_using_resource_for_delete
def test_using_resource_for_delete_with_html
RespondResource.any_instance.stubs(:destroyed?).returns(true)
delete :using_resource
assert_equal "text/html", @response.content_type
assert_equal 302, @response.status
assert_equal "www.example.com/resources", @response.location
end
def test_using_resource_for_delete_with_xml
RespondResource.any_instance.stubs(:destroyed?).returns(true)
@request.accept = "application/xml"
delete :using_resource
assert_equal "application/xml", @response.content_type
assert_equal 200, @response.status
assert_equal " ", @response.body
assert_nil @response.location
assert_equal "www.example.com/resources", @response.location
end
def test_using_resource_with_options
......@@ -692,14 +741,22 @@ def test_using_resource_with_options
assert_equal "JS", @response.body
end
def test_using_resource_with_parent
def test_using_resource_with_parent_for_get
@request.accept = "application/xml"
get :using_resource_with_parent
assert_equal "application/xml", @response.content_type
assert_equal 200, @response.status
assert_equal "XML", @response.body
end
def test_using_resource_with_parent_for_post
@request.accept = "application/xml"
post :using_resource_with_parent
assert_equal "application/xml", @response.content_type
assert_equal 201, @response.status
assert_equal "XML", @response.body
assert_equal "www.example.com/parent/11/resource/13", @response.location
assert_equal "www.example.com/parents/11/resources/13", @response.location
errors = { :name => :invalid }
RespondResource.any_instance.stubs(:errors).returns(errors)
......@@ -739,7 +796,7 @@ def test_not_acceptable
assert_equal 406, @response.status
@request.accept = "text/javascript"
get :using_resource
get :default_overwritten
assert_equal 406, @response.status
end
end
......
......@@ -458,6 +458,10 @@ def head_with_location_header
head :location => "/foo"
end
def head_with_location_object
head :location => Customer.new("david")
end
def head_with_symbolic_status
head :status => params[:status].intern
end
......@@ -618,6 +622,10 @@ def rescue_action(e)
end
private
def customer_url(customer)
request.host + "/customers/1"
end
def determine_layout
case action_name
when "hello_world", "layout_test", "rendering_without_layout",
......@@ -1084,6 +1092,13 @@ def test_head_with_location_header
assert_response :ok
end
def test_head_with_location_object
get :head_with_location_object
assert @response.body.blank?
assert_equal "www.nextangle.com/customers/1", @response.headers["Location"]
assert_response :ok
end
def test_head_with_custom_header
get :head_with_custom_header
assert @response.body.blank?
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册