From 5b7e81efec649b424037c68a93bddad1bc4e0c23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 31 Jul 2009 11:59:05 +0200 Subject: [PATCH] Allow respond_with to deal with http verb accordingly. --- .../action_controller/base/mime_responds.rb | 111 ++++++++++++------ .../test/controller/mime_responds_test.rb | 65 +++++++++- 2 files changed, 136 insertions(+), 40 deletions(-) diff --git a/actionpack/lib/action_controller/base/mime_responds.rb b/actionpack/lib/action_controller/base/mime_responds.rb index 485668d543177..9a6c8aa58ba53 100644 --- a/actionpack/lib/action_controller/base/mime_responds.rb +++ b/actionpack/lib/action_controller/base/mime_responds.rb @@ -202,11 +202,11 @@ def respond_to(*mimes, &block) # 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 # @@ -216,64 +216,101 @@ def respond_to(*mimes, &block) # # 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) - begin - respond_to(&block) - rescue ActionView::MissingTemplate => e - format = self.formats.first + 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}") - render options.merge(format => resource) + if resource.respond_to?(:"to_#{format}") + if options.delete(:no_content) + head options else - raise e + render options.merge(format => resource) end + else + raise e end end protected + # 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? + + 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 # current action. # diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 117f4ea4f036a..1db951fdfe157 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -474,6 +474,14 @@ def test_format_with_custom_response_type_and_request_headers class RespondResource undef_method :to_json + def self.model_name + @_model_name ||= ActiveModel::Name.new(name) + end + + def to_param + 13 + end + def to_xml "XML" end @@ -481,6 +489,10 @@ def to_xml def to_js "JS" end + + def errors + [] + end end class RespondWithController < ActionController::Base @@ -520,6 +532,10 @@ def _render_js(js, options) self.content_type ||= Mime::JS self.response_body = js.respond_to?(:to_js) ? js.to_js : js end + + def respond_resource_url(id) + request.host + "/respond/resource/#{id.to_param}" + end end class InheritedRespondWithController < RespondWithController @@ -593,6 +609,51 @@ def test_using_resource 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/respond/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 @@ -648,8 +709,6 @@ def test_not_acceptable end class AbstractPostController < ActionController::Base - respond_to :html, :iphone - self.view_paths = File.dirname(__FILE__) + "/../fixtures/post_test/" end @@ -658,7 +717,7 @@ class PostController < AbstractPostController around_filter :with_iphone def index - respond_to # It will use formats declared above + respond_to(:html, :iphone) end protected