From 1c16649b4895012eac76f3a7f22f3382b0034a97 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sat, 11 Mar 2006 01:23:29 +0000 Subject: Added better support for using the same actions to output for different sources depending on the Accept header [DHH] Added Base#render(:xml => xml) that works just like Base#render(:text => text), but sets the content-type to text/xml and the charset to UTF-8 [DHH] git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@3838 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- actionpack/CHANGELOG | 19 ++++ actionpack/lib/action_controller.rb | 2 + actionpack/lib/action_controller/base.rb | 13 ++- actionpack/lib/action_controller/mime_responds.rb | 53 +++++++++++ actionpack/lib/action_controller/mime_type.rb | 44 +++++++++ actionpack/lib/action_controller/request.rb | 19 ++-- actionpack/lib/action_controller/test_process.rb | 2 +- .../action_view/helpers/javascripts/prototype.js | 3 +- actionpack/test/controller/mime_responds_test.rb | 102 +++++++++++++++++++++ actionpack/test/controller/new_render_test.rb | 4 +- actionpack/test/controller/webservice_test.rb | 2 +- 11 files changed, 250 insertions(+), 13 deletions(-) create mode 100644 actionpack/lib/action_controller/mime_responds.rb create mode 100644 actionpack/lib/action_controller/mime_type.rb create mode 100644 actionpack/test/controller/mime_responds_test.rb (limited to 'actionpack') diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 222ed04d50..575bb0f534 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,24 @@ *SVN* +* Added better support for using the same actions to output for different sources depending on the Accept header [DHH]. Example: + + class WeblogController < ActionController::Base + def create + @post = Post.create(params[:post]) + + respond_to do |type| + type.js { render } # renders create.rjs + type.html { redirect_to :action => "index" } + type.xml do + headers["Location"] = url_for(:action => "show", :id => @post) + render(:nothing, :status => "201 Created") + end + end + end + end + +* Added Base#render(:xml => xml) that works just like Base#render(:text => text), but sets the content-type to text/xml and the charset to UTF-8 [DHH] + * Integration test's url_for now runs in the context of the last request (if any) so after post /products/show/1 url_for :action => 'new' will yield /product/new [Tobias Luetke] * Re-added mixed-in helper methods for the JavascriptGenerator. Moved JavascriptGenerators methods to a module that is mixed in after the helpers are added. Also fixed that variables set in the enumeration methods like #collect are set correctly. Documentation added for the enumeration methods [Rick Olson]. Examples: diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index b51e5b45bc..593a283824 100755 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -44,6 +44,7 @@ require 'action_controller/flash' require 'action_controller/filters' require 'action_controller/layout' require 'action_controller/dependencies' +require 'action_controller/mime_responds' require 'action_controller/pagination' require 'action_controller/scaffolding' require 'action_controller/helpers' @@ -67,6 +68,7 @@ ActionController::Base.class_eval do include ActionController::Benchmarking include ActionController::Rescue include ActionController::Dependencies + include ActionController::MimeResponds include ActionController::Pagination include ActionController::Scaffolding include ActionController::Helpers diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 05c8796f81..c6c052ec56 100755 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -1,3 +1,4 @@ +require 'action_controller/mime_type' require 'action_controller/request' require 'action_controller/response' require 'action_controller/routing' @@ -648,7 +649,10 @@ module ActionController #:nodoc: elsif action_name = options[:action] render_action(action_name, options[:status], options[:layout]) - + + elsif xml = options[:xml] + render_xml(xml, options[:status]) + elsif partial = options[:partial] partial = default_template_name if partial == true if collection = options[:collection] @@ -715,10 +719,15 @@ module ActionController #:nodoc: end def render_javascript(javascript, status = nil) - @response.headers['Content-Type'] = 'text/javascript' + @response.headers['Content-Type'] = 'text/javascript; charset=UTF-8' render_text(javascript, status) end + def render_xml(xml, status = nil) + @response.headers['Content-Type'] = 'text/xml; charset=UTF-8' + render_text(xml, status) + end + def render_nothing(status = nil) render_text(' ', status) end diff --git a/actionpack/lib/action_controller/mime_responds.rb b/actionpack/lib/action_controller/mime_responds.rb new file mode 100644 index 0000000000..7a60a5cc37 --- /dev/null +++ b/actionpack/lib/action_controller/mime_responds.rb @@ -0,0 +1,53 @@ +module ActionController #:nodoc: + module MimeResponds #:nodoc: + def self.included(base) + base.send(:include, ActionController::MimeResponds::InstanceMethods) + end + + module InstanceMethods + def respond_to(&block) + responder = Responder.new(block.binding) + yield responder + responder.respond + end + end + + class Responder #:nodoc: + def initialize(block_binding) + @block_binding = block_binding + @mime_type_priority = eval("request.accepts", block_binding) + @order = [] + @responses = {} + end + + for mime_type in %w( all html js xml rss atom yaml ) + eval <<-EOT + def #{mime_type}(&block) + @order << Mime::#{mime_type.upcase} + @responses[Mime::#{mime_type.upcase}] = block + end + EOT + end + + def respond + for priority in @mime_type_priority + if priority == Mime::ALL + @responses[@order.first].call + return + else + if @order.include?(priority) + @responses[priority].call + return # mime type match found, be happy and return + end + end + end + + if @order.include?(Mime::ALL) + @responses[Mime::ALL].call + else + eval 'render(:nothing => true, :status => "406 Not Acceptable")', @block_binding + end + end + end + end +end diff --git a/actionpack/lib/action_controller/mime_type.rb b/actionpack/lib/action_controller/mime_type.rb new file mode 100644 index 0000000000..0ab0221007 --- /dev/null +++ b/actionpack/lib/action_controller/mime_type.rb @@ -0,0 +1,44 @@ +module Mime + class Type < String + def initialize(string, part_of_all = true) + @part_of_all = part_of_all + super(string) + end + + def to_sym + SYMBOLIZED_MIME_TYPES[self] ? SYMBOLIZED_MIME_TYPES[self] : to_sym + end + + def ===(list) + if list.is_a?(Array) + list.include?(self) + else + super + end + end + end + + SYMBOLIZED_MIME_TYPES = { + "" => :unspecified, + "*/*" => :all, + "text/html" => :html, + "application/javascript" => :js, + "application/x-javascript" => :js, + "text/javascript" => :js, + "text/xml" => :xml, + "application/xml" => :xml, + "application/rss+xml" => :rss, + "application/rss+atom" => :atom, + "application/x-xml" => :xml, + "application/x-yaml" => :yaml + } + + ALL = Type.new "*/*" + HTML = Type.new "text/html" + JS = Type.new "text/javascript" + JAVASCRIPT = Type.new "text/javascript" + XML = Type.new "application/xml" + RSS = Type.new "application/rss+xml" + ATOM = Type.new "application/rss+atom" + YAML = Type.new "application/x-yaml" +end \ No newline at end of file diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index b55f63e618..16acc50011 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -54,13 +54,20 @@ module ActionController if @env['HTTP_X_POST_DATA_FORMAT'] case @env['HTTP_X_POST_DATA_FORMAT'].downcase.to_sym - when :yaml - @content_type = 'application/x-yaml' - when :xml - @content_type = 'application/xml' - end + when :yaml + @content_type = 'application/x-yaml' + when :xml + @content_type = 'application/xml' + end + end + + @content_type = Mime::Type.new(@content_type) + end + + def accepts + @accepts ||= (@env['HTTP_ACCEPT'].strip.blank? ? "*/*" : @env['HTTP_ACCEPT']).split(";").collect! do |mime_type| + Mime::Type.new(mime_type.strip) end - @content_type end # Returns true if the request's "X-Requested-With" header contains diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb index 07ef94aad2..afc33afc58 100644 --- a/actionpack/lib/action_controller/test_process.rb +++ b/actionpack/lib/action_controller/test_process.rb @@ -104,7 +104,7 @@ module ActionController #:nodoc: self.request_parameters = {} self.query_parameters = {} self.path_parameters = {} - @request_method = nil + @request_method, @accepts, @content_type = nil, nil, nil end private diff --git a/actionpack/lib/action_view/helpers/javascripts/prototype.js b/actionpack/lib/action_view/helpers/javascripts/prototype.js index c528a4956d..5093f40189 100644 --- a/actionpack/lib/action_view/helpers/javascripts/prototype.js +++ b/actionpack/lib/action_view/helpers/javascripts/prototype.js @@ -703,7 +703,8 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), { setRequestHeaders: function() { var requestHeaders = ['X-Requested-With', 'XMLHttpRequest', - 'X-Prototype-Version', Prototype.Version]; + 'X-Prototype-Version', Prototype.Version, + 'Accept', 'text/javascript; text/html; text/xml; */*' ]; if (this.options.method == 'post') { requestHeaders.push('Content-type', diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb new file mode 100644 index 0000000000..d78feb25e9 --- /dev/null +++ b/actionpack/test/controller/mime_responds_test.rb @@ -0,0 +1,102 @@ +require File.dirname(__FILE__) + '/../abstract_unit' + +class RespondToController < ActionController::Base + def html_xml_or_rss + respond_to do |type| + type.html { render :text => "HTML" } + type.xml { render :text => "XML" } + type.rss { render :text => "RSS" } + type.all { render :text => "Nothing" } + end + end + + def js_or_html + respond_to do |type| + type.html { render :text => "HTML" } + type.js { render :text => "JS" } + type.all { render :text => "Nothing" } + end + end + + def html_or_xml + respond_to do |type| + type.html { render :text => "HTML" } + type.xml { render :text => "XML" } + type.all { render :text => "Nothing" } + end + end + + def just_xml + respond_to do |type| + type.xml { render :text => "XML" } + end + end + + def rescue_action(e) + raise unless ActionController::MissingTemplate === e + end +end + +class MimeControllerTest < Test::Unit::TestCase + def setup + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + + @controller = RespondToController.new + @request.host = "www.example.com" + end + + def test_html + @request.env["HTTP_ACCEPT"] = "text/html" + get :js_or_html + assert_equal 'HTML', @response.body + + get :html_or_xml + assert_equal 'HTML', @response.body + + get :just_xml + assert_response 406 + end + + def test_all + @request.env["HTTP_ACCEPT"] = "*/*" + get :js_or_html + assert_equal 'HTML', @response.body # js is not part of all + + get :html_or_xml + assert_equal 'HTML', @response.body + + get :just_xml + assert_equal 'XML', @response.body + end + + def test_xml + @request.env["HTTP_ACCEPT"] = "application/xml" + get :html_xml_or_rss + assert_equal 'XML', @response.body + end + + def test_js_or_html + @request.env["HTTP_ACCEPT"] = "text/javascript; text/html" + get :js_or_html + assert_equal 'JS', @response.body + + get :html_or_xml + assert_equal 'HTML', @response.body + + get :just_xml + assert_response 406 + end + + def test_js_or_anything + @request.env["HTTP_ACCEPT"] = "text/javascript; */*" + get :js_or_html + assert_equal 'JS', @response.body + + get :html_or_xml + assert_equal 'HTML', @response.body + + get :just_xml + assert_equal 'XML', @response.body + end +end \ No newline at end of file diff --git a/actionpack/test/controller/new_render_test.rb b/actionpack/test/controller/new_render_test.rb index 2701b6a462..8359e190a0 100644 --- a/actionpack/test/controller/new_render_test.rb +++ b/actionpack/test/controller/new_render_test.rb @@ -544,14 +544,14 @@ EOS def test_update_page get :update_page assert_template nil - assert_equal 'text/javascript', @response.headers['Content-Type'] + assert_equal 'text/javascript; charset=UTF-8', @response.headers['Content-Type'] assert_equal 2, @response.body.split($/).length end def test_update_page_with_instance_variables get :update_page_with_instance_variables assert_template nil - assert_equal 'text/javascript', @response.headers['Content-Type'] + assert_equal 'text/javascript; charset=UTF-8', @response.headers['Content-Type'] assert_match /balance/, @response.body assert_match /\$37/, @response.body end diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb index 6275cfb6d0..2243397d87 100644 --- a/actionpack/test/controller/webservice_test.rb +++ b/actionpack/test/controller/webservice_test.rb @@ -71,7 +71,7 @@ class WebServiceTest < Test::Unit::TestCase end def test_register_and_use_xml_simple - ActionController::Base.param_parsers['application/xml'] = :xml_simple + ActionController::Base.param_parsers['application/xml'] = Proc.new { |data| XmlSimple.xml_in(data, 'ForceArray' => false) } process('POST', 'application/xml', 'content...SimpleXml' ) assert_equal 'summary, title', @controller.response.body assert @controller.params.has_key?(:summary) -- cgit v1.2.3