diff options
author | José Valim <jose.valim@gmail.com> | 2009-07-29 21:22:56 +0200 |
---|---|---|
committer | José Valim <jose.valim@gmail.com> | 2009-07-29 21:22:56 +0200 |
commit | e566fc0577f336be35145b815f3fd0a554eab564 (patch) | |
tree | 718f31723bb6e6d4c9002b4e2e390b8afe33f82d /actionpack/lib | |
parent | 7d56fd9f08795e7dddac9d31b34d707b83023c6b (diff) | |
parent | d209aea7d88849b7c7a080d729045ce7c051ef1f (diff) | |
download | rails-e566fc0577f336be35145b815f3fd0a554eab564.tar.gz rails-e566fc0577f336be35145b815f3fd0a554eab564.tar.bz2 rails-e566fc0577f336be35145b815f3fd0a554eab564.zip |
Merge branch 'master' of git://github.com/rails/rails
Diffstat (limited to 'actionpack/lib')
6 files changed, 279 insertions, 66 deletions
diff --git a/actionpack/lib/action_controller/base/mime_responds.rb b/actionpack/lib/action_controller/base/mime_responds.rb index ed0d58dba1..f4a4007a43 100644 --- a/actionpack/lib/action_controller/base/mime_responds.rb +++ b/actionpack/lib/action_controller/base/mime_responds.rb @@ -1,5 +1,55 @@ module ActionController #:nodoc: module MimeResponds #:nodoc: + extend ActiveSupport::Concern + + included do + class_inheritable_reader :mimes_for_respond_to + clear_respond_to + end + + module ClassMethods + # Defines mimes that are rendered by default when invoking respond_with. + # + # Examples: + # + # respond_to :html, :xml, :json + # + # All actions on your controller will respond to :html, :xml and :json. + # + # But if you want to specify it based on your actions, you can use only and + # except: + # + # respond_to :html + # respond_to :xml, :json, :except => [ :edit ] + # + # The definition above explicits that all actions respond to :html. And all + # actions except :edit respond to :xml and :json. + # + # You can specify also only parameters: + # + # respond_to :rjs, :only => :create + # + def respond_to(*mimes) + options = mimes.extract_options! + + only_actions = Array(options.delete(:only)) + except_actions = Array(options.delete(:except)) + + mimes.each do |mime| + mime = mime.to_sym + mimes_for_respond_to[mime] = {} + mimes_for_respond_to[mime][:only] = only_actions unless only_actions.empty? + mimes_for_respond_to[mime][:except] = except_actions unless except_actions.empty? + end + end + + # Clear all mimes in respond_to. + # + def clear_respond_to + write_inheritable_attribute(:mimes_for_respond_to, ActiveSupport::OrderedHash.new) + end + end + # Without web-service support, an action which collects the data for displaying a list of people # might look something like this: # @@ -92,50 +142,187 @@ module ActionController #:nodoc: # environment.rb as follows. # # Mime::Type.register "image/jpg", :jpg - def respond_to(*types, &block) - raise ArgumentError, "respond_to takes either types or a block, never both" unless types.any? ^ block - block ||= lambda { |responder| types.each { |type| responder.send(type) } } - responder = Responder.new(self) - block.call(responder) - responder.respond - end + # + # 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? - class Responder #:nodoc: - - def initialize(controller) - @controller = controller - @request = controller.request - @response = controller.response + resource = options.delete(:with) + responder = Responder.new - @mime_type_priority = @request.formats + mimes = collect_mimes_from_class_level if mimes.empty? + mimes.each { |mime| responder.send(mime) } + block.call(responder) if block_given? - @order = [] - @responses = {} + if format = request.negotiate_mime(responder.order) + respond_to_block_or_template_or_resource(format, resource, + options, &responder.response_for(format)) + else + head :not_acceptable end + end - def custom(mime_type, &block) - mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s) + # 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 - @order << mime_type + protected - @responses[mime_type] ||= Proc.new do - # TODO: Remove this when new base is merged in - @controller.formats = [mime_type.to_sym] - @controller.content_type = mime_type - @controller.template.formats = [mime_type.to_sym] + def respond_to_block_or_template_or_resource(format, resource, options) + self.formats = [format.to_sym] + return yield if block_given? - block_given? ? block.call : @controller.send(:render, :action => @controller.action_name) + 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 + + def initialize + @order, @responses = [], {} + end def any(*args, &block) if args.any? args.each { |type| send(type, &block) } else - custom(@mime_type_priority.first, &block) + custom(Mime::ALL, &block) end end - + alias :all :any + + def custom(mime_type, &block) + mime_type = mime_type.is_a?(Mime::Type) ? mime_type : Mime::Type.lookup(mime_type.to_s) + + @order << mime_type + @responses[mime_type] ||= block + end + + def response_for(mime) + @responses[mime] || @responses[Mime::ALL] + end + def self.generate_method_for_mime(mime) sym = mime.is_a?(Symbol) ? mime : mime.to_sym const = sym.to_s.upcase @@ -152,7 +339,7 @@ module ActionController #:nodoc: def method_missing(symbol, &block) mime_constant = Mime.const_get(symbol.to_s.upcase) - + if Mime::SET.include?(mime_constant) self.class.generate_method_for_mime(mime_constant) send(symbol, &block) @@ -161,25 +348,6 @@ module ActionController #:nodoc: end end - def respond - for priority in @mime_type_priority - if priority == Mime::ALL - @responses[@order.first].call - return - else - if @responses[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 - @controller.send :head, :not_acceptable - end - end end end end diff --git a/actionpack/lib/action_controller/base/renderer.rb b/actionpack/lib/action_controller/base/renderer.rb index 2fab501302..572da451ff 100644 --- a/actionpack/lib/action_controller/base/renderer.rb +++ b/actionpack/lib/action_controller/base/renderer.rb @@ -11,11 +11,10 @@ module ActionController def render(options) super - options[:_template] ||= _action_view._partial self.content_type ||= begin mime = options[:_template].mime_type formats.include?(mime && mime.to_sym) || formats.include?(:all) ? mime : Mime::Type.lookup_by_extension(formats.first) - end + end.to_s response_body end diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 27f27e27fe..cc989d6625 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -84,7 +84,7 @@ module Mime end def lookup_by_extension(extension) - EXTENSION_LOOKUP[extension] + EXTENSION_LOOKUP[extension.to_s] end # Registers an alias that's not used on mime type lookup, but can be referenced directly. Especially useful for diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb index 7c28cac419..68f37d2f65 100644 --- a/actionpack/lib/action_dispatch/http/mime_types.rb +++ b/actionpack/lib/action_dispatch/http/mime_types.rb @@ -2,7 +2,6 @@ # http://www.iana.org/assignments/media-types/ Mime::Type.register "text/html", :html, %w( application/xhtml+xml ), %w( xhtml ) -Mime::Type.register "*/*", :all Mime::Type.register "text/plain", :text, [], %w(txt) Mime::Type.register "text/javascript", :js, %w( application/javascript application/x-javascript ) Mime::Type.register "text/css", :css @@ -18,4 +17,7 @@ Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form # http://www.ietf.org/rfc/rfc4627.txt # http://www.json.org/JSONRequest.html -Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest )
\ No newline at end of file +Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest ) + +# Create Mime::ALL but do not add it to the SET. +Mime::ALL = Mime::Type.new("*/*", :all, []) diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 3f23a5af7a..5f9463eb91 100755 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -161,7 +161,7 @@ module ActionDispatch # GET /posts/5.xml | request.format => Mime::XML # GET /posts/5.xhtml | request.format => Mime::HTML # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt> - + # def format(view_path = []) @env["action_dispatch.request.format"] ||= if parameters[:format] @@ -173,13 +173,11 @@ module ActionDispatch end end + # Expand raw_formats by converting Mime::ALL to the Mime::SET. + # def formats if ActionController::Base.use_accept_header - if param = parameters[:format] - Array.wrap(Mime[param]) - else - accepts.dup - end.tap do |ret| + raw_formats.tap do |ret| if ret == ONLY_ALL ret.replace Mime::SET elsif all = ret.index(Mime::ALL) @@ -187,7 +185,7 @@ module ActionDispatch end end else - [format] + Mime::SET + raw_formats + Mime::SET end end @@ -232,7 +230,7 @@ module ActionDispatch def xml_http_request? !(@env['HTTP_X_REQUESTED_WITH'] !~ /XMLHttpRequest/i) end - alias xhr? :xml_http_request? + alias :xhr? :xml_http_request? # Which IP addresses are "trusted proxies" that can be stripped from # the right-hand-side of X-Forwarded-For @@ -485,7 +483,35 @@ EOM session['flash'] || {} end + # Receives an array of mimes and return the first user sent mime that + # matches the order array. + # + def negotiate_mime(order) + raw_formats.each do |priority| + if priority == Mime::ALL + return order.first + elsif order.include?(priority) + return priority + end + end + + order.include?(Mime::ALL) ? formats.first : nil + end + private + + def raw_formats + if ActionController::Base.use_accept_header + if param = parameters[:format] + Array.wrap(Mime[param]) + else + accepts.dup + end + else + [format] + end + end + def named_host?(host) !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host)) end diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb index 0f122d9232..4fd7f7d83c 100644 --- a/actionpack/lib/action_view/helpers/active_model_helper.rb +++ b/actionpack/lib/action_view/helpers/active_model_helper.rb @@ -160,11 +160,24 @@ module ActionView # # error_messages_for 'user' # + # You can also supply an object: + # + # error_messages_for @user + # + # This will use the last part of the model name in the presentation. For instance, if + # this is a MyKlass::User object, this will use "user" as the name in the String. This + # is taken from MyKlass::User.model_name.human, which can be overridden. + # # To specify more than one object, you simply list them; optionally, you can add an extra <tt>:object_name</tt> parameter, which # will be the name used in the header message: # # error_messages_for 'user_common', 'user', :object_name => 'user' # + # You can also use a number of objects, which will have the same naming semantics + # as a single object. + # + # error_messages_for @user, @post + # # If the objects cannot be located as instance variables, you can add an extra <tt>:object</tt> parameter which gives the actual # object (or array of objects to use): # @@ -176,15 +189,20 @@ module ActionView def error_messages_for(*params) options = params.extract_options!.symbolize_keys - if object = options.delete(:object) - objects = [object].flatten - else - objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact + objects = Array.wrap(options.delete(:object) || params).map do |object| + unless object.respond_to?(:to_model) + object = instance_variable_get("@#{object}") + object = convert_to_model(object) + else + object = object.to_model + options[:object_name] ||= object.class.model_name.human + end + object end - objects.map! {|o| convert_to_model(o) } + objects.compact! - count = objects.inject(0) {|sum, object| sum + object.errors.count } + count = objects.inject(0) {|sum, object| sum + object.errors.count } unless count.zero? html = {} [:id, :class].each do |key| |