aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller/base/mime_responds.rb
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_controller/base/mime_responds.rb')
-rw-r--r--actionpack/lib/action_controller/base/mime_responds.rb262
1 files changed, 215 insertions, 47 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