diff options
-rw-r--r-- | actionpack/lib/action_controller/metal/mime_responds.rb | 128 | ||||
-rw-r--r-- | actionpack/test/controller/mime_responds_test.rb | 117 |
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 |