diff --git a/actionpack/lib/action_controller/base/mime_responds.rb b/actionpack/lib/action_controller/base/mime_responds.rb index 0ce6660c98..f7c1b071e7 100644 --- a/actionpack/lib/action_controller/base/mime_responds.rb +++ b/actionpack/lib/action_controller/base/mime_responds.rb @@ -145,8 +145,45 @@ module ActionController #:nodoc: # environment.rb as follows. # # Mime::Type.register "image/jpg", :jpg + # + # Respond to also allows you to specify a common block for different formats by using any: + # + # def index + # @people = Person.find(:all) + # + # respond_to do |format| + # format.html + # format.any(:xml, :json) { render request.format.to_sym => @people } + # end + # end + # + # In the example above, if the format is xml, it will render: + # + # render :xml => @people + # + # Or if the format is json: + # + # render :json => @people + # + # Since this is a common pattern, you can use the class method respond_to + # with the respond_with method to have the same results: + # + # class PeopleController < ApplicationController + # respond_to :html, :xml, :json + # + # def index + # @people = Person.find(:all) + # respond_with(@person) + # end + # end + # + # 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 block.call(responder) if block_given? @@ -154,41 +191,117 @@ module ActionController #:nodoc: mimes.each { |mime| responder.send(mime) } if format = request.negotiate_mime(responder.order) - # TODO It should be just: self.formats = [ :foo ] - self.formats = [format.to_sym] - self.content_type = format - self.template.formats = [format.to_sym] - - if response = responder.response_for(format) - response.call - else - default_render - end + respond_to_block_or_template_or_resource(format, resource, + options, &responder.response_for(format)) else head :not_acceptable end end - protected + # 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 + # formats allowed: + # + # class PeopleController < ApplicationController + # respond_to :html, :xml, :json + # + # def index + # @people = Person.find(:all) + # respond_with(@person) + # 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. + # + # 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: + # + # class PeopleController < ApplicationController + # respond_to :xml, :json + # + # 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) + # 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: + # + # class PeopleController < ApplicationController + # respond_to :html, :xml, :json + # + # def create + # @person = Person.new(params[:person]) + # + # if @person.save + # options = { :status => :ok, :location => person_url(@person) } + # + # 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 + # end + # + def respond_with(resource, options={}, &block) + respond_to(options.merge!(:with => resource), &block) + end - # Collect mimes declared in the class method respond_to valid for the - # current action. - # - def collect_mimes_from_class_level #:nodoc: - action = action_name.to_sym + protected - mimes_for_respond_to.keys.select do |mime| - config = mimes_for_respond_to[mime] + def respond_to_block_or_template_or_resource(format, resource, options) + # TODO It should be just: self.formats = [ :foo ] + self.formats = [format.to_sym] + self.content_type = format + self.template.formats = [format.to_sym] - if config[:except] - !config[:except].include?(action) - elsif config[:only] - config[:only].include?(action) - else - true - end + return yield if block_given? + + 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 end + end + + # Collect mimes declared in the class method respond_to valid for the + # current action. + # + def collect_mimes_from_class_level #:nodoc: + action = action_name.to_sym + + mimes_for_respond_to.keys.select do |mime| + config = mimes_for_respond_to[mime] + + if config[:except] + !config[:except].include?(action) + elsif config[:only] + config[:only].include?(action) + else + true + end + end + end class Responder #:nodoc: attr_accessor :order diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 48b343272e..369d683d23 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -471,8 +471,20 @@ class RespondToControllerTest < ActionController::TestCase end end +class RespondResource + undef_method :to_json + + def to_xml + "XML" + end + + def to_js + "JS" + end +end + class RespondWithController < ActionController::Base - respond_to :html + respond_to :html, :json respond_to :xml, :except => :using_defaults respond_to :js, :only => :using_defaults @@ -485,6 +497,23 @@ class RespondWithController < ActionController::Base def using_defaults_with_type_list respond_to(:js, :xml) end + + def using_resource + respond_with(RespondResource.new) + end + + def using_resource_with_options + respond_with(RespondResource.new, :status => :unprocessable_entity) do |format| + format.js + end + end + +protected + + def _render_js(js, options) + self.content_type ||= Mime::JS + self.response_body = js.respond_to?(:to_js) ? js.to_js : js + end end class RespondWithControllerTest < ActionController::TestCase @@ -530,6 +559,37 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal "
Hello world!
\n", @response.body end + def test_using_resource + @request.accept = "text/html" + get :using_resource + assert_equal "text/html", @response.content_type + assert_equal "Hello world!", @response.body + + @request.accept = "application/xml" + get :using_resource + assert_equal "application/xml", @response.content_type + assert_equal "XML", @response.body + + @request.accept = "application/json" + assert_raise ActionView::MissingTemplate do + get :using_resource + end + end + + def test_using_resource_with_options + @request.accept = "application/xml" + get :using_resource_with_options + assert_equal "application/xml", @response.content_type + assert_equal 422, @response.status + assert_equal "XML", @response.body + + @request.accept = "text/javascript" + get :using_resource_with_options + assert_equal "text/javascript", @response.content_type + assert_equal 422, @response.status + assert_equal "JS", @response.body + end + def test_not_acceptable @request.accept = "application/xml" get :using_defaults @@ -538,6 +598,14 @@ class RespondWithControllerTest < ActionController::TestCase @request.accept = "text/html" get :using_defaults_with_type_list assert_equal 406, @response.status + + @request.accept = "application/json" + get :using_defaults_with_type_list + assert_equal 406, @response.status + + @request.accept = "text/javascript" + get :using_resource + assert_equal 406, @response.status end end diff --git a/actionpack/test/fixtures/respond_with/using_resource.html.erb b/actionpack/test/fixtures/respond_with/using_resource.html.erb new file mode 100644 index 0000000000..6769dd60bd --- /dev/null +++ b/actionpack/test/fixtures/respond_with/using_resource.html.erb @@ -0,0 +1 @@ +Hello world! \ No newline at end of file