aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb128
-rw-r--r--actionpack/test/controller/mime_responds_test.rb117
2 files changed, 187 insertions, 58 deletions
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index f4a4007a43..9a6c8aa58b 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -177,19 +177,21 @@ module ActionController #:nodoc:
# Be sure to check respond_with and respond_to documentation for more examples.
#
def respond_to(*mimes, &block)
- options = mimes.extract_options!
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
- resource = options.delete(:with)
responder = Responder.new
-
mimes = collect_mimes_from_class_level if mimes.empty?
mimes.each { |mime| responder.send(mime) }
block.call(responder) if block_given?
if format = request.negotiate_mime(responder.order)
- respond_to_block_or_template_or_resource(format, resource,
- options, &responder.response_for(format))
+ self.formats = [format.to_sym]
+
+ if response = responder.response_for(format)
+ response.call
+ else
+ default_render
+ end
else
head :not_acceptable
end
@@ -200,11 +202,11 @@ module ActionController #:nodoc:
# formats allowed:
#
# class PeopleController < ApplicationController
- # respond_to :html, :xml, :json
+ # respond_to :xml, :json
#
# def index
# @people = Person.find(:all)
- # respond_with(@person)
+ # respond_with(@people)
# end
# end
#
@@ -214,67 +216,99 @@ module ActionController #:nodoc:
#
# If neither are available, it will raise an error.
#
- # Extra parameters given to respond_with are used when :to_format is invoked.
- # This allows you to set status and location for several formats at the same
- # time. Consider this restful controller response on create for both xml
- # and json formats:
+ # respond_with holds semantics for each HTTP verb. The example above cover
+ # GET requests. Let's check a POST request example:
+ #
+ # def create
+ # @person = Person.new(params[:person])
+ # @person.save
+ # 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).
+ #
+ # If no error was found, it will render the @people resource, with status
+ # created (201) and location header set to person_url(@people).
+ #
+ # If you also want to provide html behavior in the method above, you can
+ # supply a block to customize it:
#
# class PeopleController < ApplicationController
- # respond_to :xml, :json
+ # respond_to :html, :xml, :json # Add :html to respond_to definition
#
# def create
- # @person = Person.new(params[:person])
- #
- # if @person.save
- # respond_with(@person, :status => :ok, :location => person_url(@person))
- # else
- # respond_with(@person.errors, :status => :unprocessable_entity)
+ # @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
# end
# end
#
- # Finally, respond_with also accepts blocks, as in respond_to. Let's take
- # the same controller and create action above and add common html behavior:
+ # It works similarly for PUT requests:
#
- # class PeopleController < ApplicationController
- # respond_to :html, :xml, :json
+ # def update
+ # @person = Person.find(params[:id])
+ # @person.update_attributes(params[:person])
+ # respond_with(@person)
+ # end
#
- # def create
- # @person = Person.new(params[:person])
+ # In case of failures, it works as POST requests, but in success failures
+ # it just reply status ok (200) to the client.
#
- # if @person.save
- # options = { :status => :ok, :location => person_url(@person) }
+ # A DELETE request also works in the same way:
#
- # respond_with(@person, options) do |format|
- # format.html { redirect_to options[:location] }
- # end
- # else
- # respond_with(@person.errors, :status => :unprocessable_entity) do
- # format.html { render :action => :new }
- # end
- # end
- # end
+ # def destroy
+ # @person = Person.find(params[:id])
+ # @person.destroy
+ # respond_with(@person)
# end
#
+ # It just replies with status ok, indicating the record was successfuly
+ # destroyed.
+ #
def respond_with(resource, options={}, &block)
- respond_to(options.merge!(:with => resource), &block)
+ respond_to(&block)
+ rescue ActionView::MissingTemplate => e
+ format = self.formats.first
+ resource = normalize_resource_options_by_verb(resource, options)
+
+ if resource.respond_to?(:"to_#{format}")
+ if options.delete(:no_content)
+ head options
+ else
+ render options.merge(format => resource)
+ end
+ else
+ raise e
+ end
end
protected
- def respond_to_block_or_template_or_resource(format, resource, options)
- self.formats = [format.to_sym]
- return yield if block_given?
+ # 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?
- begin
- default_render
- rescue ActionView::MissingTemplate => e
- if resource && resource.respond_to?(:"to_#{format.to_sym}")
- render options.merge(format.to_sym => resource)
- else
- raise e
- end
+ if has_errors && (request.post? || request.put?)
+ options.reverse_merge!(:status => :unprocessable_entity)
+ 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)
end
+
+ return resource
end
# Collect mimes declared in the class method respond_to valid for the
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index 117f4ea4f0..1d27e749ae 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -474,6 +474,14 @@ end
class RespondResource
undef_method :to_json
+ def self.model_name
+ @_model_name ||= ActiveModel::Name.new("resource")
+ end
+
+ def to_param
+ 13
+ end
+
def to_xml
"XML"
end
@@ -481,6 +489,20 @@ class RespondResource
def to_js
"JS"
end
+
+ def errors
+ []
+ end
+end
+
+class ParentResource
+ def self.model_name
+ @_model_name ||= ActiveModel::Name.new("parent")
+ end
+
+ def to_param
+ 11
+ end
end
class RespondWithController < ActionController::Base
@@ -498,6 +520,12 @@ class RespondWithController < ActionController::Base
respond_to(:js, :xml)
end
+ def default_overwritten
+ respond_to do |format|
+ format.html { render :text => "HTML" }
+ end
+ end
+
def using_resource
respond_with(RespondResource.new)
end
@@ -508,10 +536,8 @@ class RespondWithController < ActionController::Base
end
end
- def default_overwritten
- respond_to do |format|
- format.html { render :text => "HTML" }
- end
+ def using_resource_with_parent
+ respond_with([ParentResource.new, RespondResource.new])
end
protected
@@ -520,6 +546,14 @@ protected
self.content_type ||= Mime::JS
self.response_body = js.respond_to?(:to_js) ? js.to_js : js
end
+
+ def resource_url(resource)
+ request.host + "/resource/#{resource.to_param}"
+ end
+
+ def parent_resource_url(parent, resource)
+ request.host + "/parent/#{parent.to_param}/resource/#{resource.to_param}"
+ end
end
class InheritedRespondWithController < RespondWithController
@@ -576,6 +610,12 @@ class RespondWithControllerTest < ActionController::TestCase
assert_equal "<p>Hello world!</p>\n", @response.body
end
+ def test_default_overwritten
+ get :default_overwritten
+ assert_equal "text/html", @response.content_type
+ assert_equal "HTML", @response.body
+ end
+
def test_using_resource
@request.accept = "text/html"
get :using_resource
@@ -593,6 +633,51 @@ class RespondWithControllerTest < ActionController::TestCase
end
end
+ def test_using_resource_for_post
+ @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
+
+ errors = { :name => :invalid }
+ RespondResource.any_instance.stubs(:errors).returns(errors)
+ post :using_resource
+ assert_equal "application/xml", @response.content_type
+ assert_equal 422, @response.status
+ assert_equal errors.to_xml, @response.body
+ assert_nil @response.location
+ end
+
+ def test_using_resource_for_put
+ @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
+
+ errors = { :name => :invalid }
+ RespondResource.any_instance.stubs(:errors).returns(errors)
+ put :using_resource
+ assert_equal "application/xml", @response.content_type
+ assert_equal 422, @response.status
+ assert_equal errors.to_xml, @response.body
+ assert_nil @response.location
+ end
+
+ def test_using_resource_for_delete
+ @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
+ end
+
def test_using_resource_with_options
@request.accept = "application/xml"
get :using_resource_with_options
@@ -607,10 +692,22 @@ class RespondWithControllerTest < ActionController::TestCase
assert_equal "JS", @response.body
end
- def test_default_overwritten
- get :default_overwritten
- assert_equal "text/html", @response.content_type
- assert_equal "HTML", @response.body
+ def test_using_resource_with_parent
+ @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
+
+ errors = { :name => :invalid }
+ RespondResource.any_instance.stubs(:errors).returns(errors)
+ post :using_resource
+ assert_equal "application/xml", @response.content_type
+ assert_equal 422, @response.status
+ assert_equal errors.to_xml, @response.body
+ assert_nil @response.location
end
def test_clear_respond_to
@@ -648,8 +745,6 @@ class RespondWithControllerTest < ActionController::TestCase
end
class AbstractPostController < ActionController::Base
- respond_to :html, :iphone
-
self.view_paths = File.dirname(__FILE__) + "/../fixtures/post_test/"
end
@@ -658,7 +753,7 @@ class PostController < AbstractPostController
around_filter :with_iphone
def index
- respond_to # It will use formats declared above
+ respond_to(:html, :iphone)
end
protected