From aed135d3e261cbee153a35fcfbeb47e2e02b12e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 6 Aug 2009 23:48:48 +0200 Subject: Renamed presenter to renderer, added some documentation and defined its API. --- actionpack/examples/very_simple.rb | 4 +- actionpack/lib/abstract_controller.rb | 20 +- actionpack/lib/abstract_controller/helpers.rb | 2 +- actionpack/lib/abstract_controller/layouts.rb | 2 +- actionpack/lib/abstract_controller/renderer.rb | 156 --------------- .../abstract_controller/rendering_controller.rb | 156 +++++++++++++++ actionpack/lib/action_controller.rb | 4 +- actionpack/lib/action_controller/base.rb | 4 +- actionpack/lib/action_controller/metal/layouts.rb | 2 +- .../lib/action_controller/metal/mime_responds.rb | 162 +--------------- .../lib/action_controller/metal/render_options.rb | 10 +- actionpack/lib/action_controller/metal/renderer.rb | 215 ++++++++++++++++----- .../metal/rendering_controller.rb | 66 +++++++ .../lib/action_controller/metal/streaming.rb | 2 +- .../lib/action_controller/metal/verification.rb | 4 +- .../abstract_controller_test.rb | 6 +- actionpack/test/abstract_controller/helper_test.rb | 4 +- .../test/abstract_controller/layouts_test.rb | 2 +- actionpack/test/controller/mime_responds_test.rb | 22 ++- 19 files changed, 448 insertions(+), 395 deletions(-) delete mode 100644 actionpack/lib/abstract_controller/renderer.rb create mode 100644 actionpack/lib/abstract_controller/rendering_controller.rb create mode 100644 actionpack/lib/action_controller/metal/rendering_controller.rb (limited to 'actionpack') diff --git a/actionpack/examples/very_simple.rb b/actionpack/examples/very_simple.rb index 422c4c3298..6714185172 100644 --- a/actionpack/examples/very_simple.rb +++ b/actionpack/examples/very_simple.rb @@ -6,7 +6,7 @@ require "action_controller" class Kaigi < ActionController::Metal include AbstractController::Callbacks include ActionController::RackConvenience - include ActionController::Renderer + include ActionController::RenderingController include ActionController::Layouts include ActionView::Context @@ -47,4 +47,4 @@ app = Rack::Builder.new do map("/kaigi/alt") { run Kaigi.action(:alt) } end.to_app -Rack::Handler::Mongrel.run app, :Port => 3000 \ No newline at end of file +Rack::Handler::Mongrel.run app, :Port => 3000 diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index f020abaa45..cdeb55b915 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -2,15 +2,15 @@ require "active_support/core_ext/module/attr_internal" require "active_support/core_ext/module/delegation" module AbstractController - autoload :Base, "abstract_controller/base" - autoload :Benchmarker, "abstract_controller/benchmarker" - autoload :Callbacks, "abstract_controller/callbacks" - autoload :Helpers, "abstract_controller/helpers" - autoload :Layouts, "abstract_controller/layouts" - autoload :Logger, "abstract_controller/logger" - autoload :Renderer, "abstract_controller/renderer" + autoload :Base, "abstract_controller/base" + autoload :Benchmarker, "abstract_controller/benchmarker" + autoload :Callbacks, "abstract_controller/callbacks" + autoload :Helpers, "abstract_controller/helpers" + autoload :Layouts, "abstract_controller/layouts" + autoload :Logger, "abstract_controller/logger" + autoload :RenderingController, "abstract_controller/rendering_controller" # === Exceptions - autoload :ActionNotFound, "abstract_controller/exceptions" - autoload :DoubleRenderError, "abstract_controller/exceptions" - autoload :Error, "abstract_controller/exceptions" + autoload :ActionNotFound, "abstract_controller/exceptions" + autoload :DoubleRenderError, "abstract_controller/exceptions" + autoload :Error, "abstract_controller/exceptions" end diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index 5efa37fde3..04eaa02441 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -2,7 +2,7 @@ module AbstractController module Helpers extend ActiveSupport::Concern - include Renderer + include RenderingController included do extlib_inheritable_accessor(:_helpers) { Module.new } diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 038598a3b3..0cb3d166f7 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -2,7 +2,7 @@ module AbstractController module Layouts extend ActiveSupport::Concern - include Renderer + include RenderingController included do extlib_inheritable_accessor(:_layout_conditions) { Hash.new } diff --git a/actionpack/lib/abstract_controller/renderer.rb b/actionpack/lib/abstract_controller/renderer.rb deleted file mode 100644 index da57d0ff4d..0000000000 --- a/actionpack/lib/abstract_controller/renderer.rb +++ /dev/null @@ -1,156 +0,0 @@ -require "abstract_controller/logger" - -module AbstractController - module Renderer - extend ActiveSupport::Concern - - include AbstractController::Logger - - included do - attr_internal :formats - - extlib_inheritable_accessor :_view_paths - - self._view_paths ||= ActionView::PathSet.new - end - - # An instance of a view class. The default view class is ActionView::Base - # - # The view class must have the following methods: - # View.for_controller[controller] Create a new ActionView instance for a - # controller - # View#render_partial[options] - # - responsible for setting options[:_template] - # - Returns String with the rendered partial - # options:: see _render_partial in ActionView::Base - # View#render_template[template, layout, options, partial] - # - Returns String with the rendered template - # template:: The template to render - # layout:: The layout to render around the template - # options:: See _render_template_with_layout in ActionView::Base - # partial:: Whether or not the template to render is a partial - # - # Override this method in a to change the default behavior. - def view_context - @_view_context ||= ActionView::Base.for_controller(self) - end - - # Mostly abstracts the fact that calling render twice is a DoubleRenderError. - # Delegates render_to_body and sticks the result in self.response_body. - def render(*args) - if response_body - raise AbstractController::DoubleRenderError, "OMG" - end - - self.response_body = render_to_body(*args) - end - - # Raw rendering of a template to a Rack-compatible body. - # - # ==== Options - # _partial_object:: The object that is being rendered. If this - # exists, we are in the special case of rendering an object as a partial. - # - # :api: plugin - def render_to_body(options = {}) - # TODO: Refactor so we can just use the normal template logic for this - if options.key?(:partial) - view_context.render_partial(options) - else - _determine_template(options) - _render_template(options) - end - end - - # Raw rendering of a template to a string. Just convert the results of - # render_to_body into a String. - # - # :api: plugin - def render_to_string(options = {}) - AbstractController::Renderer.body_to_s(render_to_body(options)) - end - - # Renders the template from an object. - # - # ==== Options - # _template:: The template to render - # _layout:: The layout to wrap the template in (optional) - # _partial:: Whether or not the template to be rendered is a partial - def _render_template(options) - view_context.render_template(options) - end - - # The list of view paths for this controller. See ActionView::ViewPathSet for - # more details about writing custom view paths. - def view_paths - _view_paths - end - - # Return a string representation of a Rack-compatible response body. - def self.body_to_s(body) - if body.respond_to?(:to_str) - body - else - strings = [] - body.each { |part| strings << part.to_s } - body.close if body.respond_to?(:close) - strings.join - end - end - - private - # Take in a set of options and determine the template to render - # - # ==== Options - # _template:: If this is provided, the search is over - # _template_name<#to_s>:: The name of the template to look up. Otherwise, - # use the current action name. - # _prefix:: The prefix to look inside of. In a file system, this corresponds - # to a directory. - # _partial:: Whether or not the file to look up is a partial - def _determine_template(options) - name = (options[:_template_name] || action_name).to_s - - options[:_template] ||= view_paths.find_by_parts( - name, { :formats => formats }, options[:_prefix], options[:_partial] - ) - end - - module ClassMethods - # Append a path to the list of view paths for this controller. - # - # ==== Parameters - # path:: If a String is provided, it gets converted into - # the default view path. You may also provide a custom view path - # (see ActionView::ViewPathSet for more information) - def append_view_path(path) - self.view_paths << path - end - - # Prepend a path to the list of view paths for this controller. - # - # ==== Parameters - # path:: If a String is provided, it gets converted into - # the default view path. You may also provide a custom view path - # (see ActionView::ViewPathSet for more information) - def prepend_view_path(path) - self.view_paths.unshift(path) - end - - # A list of all of the default view paths for this controller. - def view_paths - self._view_paths - end - - # Set the view paths. - # - # ==== Parameters - # paths:: If a ViewPathSet is provided, use that; - # otherwise, process the parameter into a ViewPathSet. - def view_paths=(paths) - self._view_paths = paths.is_a?(ActionView::PathSet) ? - paths : ActionView::Base.process_view_paths(paths) - end - end - end -end diff --git a/actionpack/lib/abstract_controller/rendering_controller.rb b/actionpack/lib/abstract_controller/rendering_controller.rb new file mode 100644 index 0000000000..23cd71e0bd --- /dev/null +++ b/actionpack/lib/abstract_controller/rendering_controller.rb @@ -0,0 +1,156 @@ +require "abstract_controller/logger" + +module AbstractController + module RenderingController + extend ActiveSupport::Concern + + include AbstractController::Logger + + included do + attr_internal :formats + + extlib_inheritable_accessor :_view_paths + + self._view_paths ||= ActionView::PathSet.new + end + + # An instance of a view class. The default view class is ActionView::Base + # + # The view class must have the following methods: + # View.for_controller[controller] Create a new ActionView instance for a + # controller + # View#render_partial[options] + # - responsible for setting options[:_template] + # - Returns String with the rendered partial + # options:: see _render_partial in ActionView::Base + # View#render_template[template, layout, options, partial] + # - Returns String with the rendered template + # template:: The template to render + # layout:: The layout to render around the template + # options:: See _render_template_with_layout in ActionView::Base + # partial:: Whether or not the template to render is a partial + # + # Override this method in a to change the default behavior. + def view_context + @_view_context ||= ActionView::Base.for_controller(self) + end + + # Mostly abstracts the fact that calling render twice is a DoubleRenderError. + # Delegates render_to_body and sticks the result in self.response_body. + def render(*args) + if response_body + raise AbstractController::DoubleRenderError, "OMG" + end + + self.response_body = render_to_body(*args) + end + + # Raw rendering of a template to a Rack-compatible body. + # + # ==== Options + # _partial_object:: The object that is being rendered. If this + # exists, we are in the special case of rendering an object as a partial. + # + # :api: plugin + def render_to_body(options = {}) + # TODO: Refactor so we can just use the normal template logic for this + if options.key?(:partial) + view_context.render_partial(options) + else + _determine_template(options) + _render_template(options) + end + end + + # Raw rendering of a template to a string. Just convert the results of + # render_to_body into a String. + # + # :api: plugin + def render_to_string(options = {}) + AbstractController::RenderingController.body_to_s(render_to_body(options)) + end + + # Renders the template from an object. + # + # ==== Options + # _template:: The template to render + # _layout:: The layout to wrap the template in (optional) + # _partial:: Whether or not the template to be rendered is a partial + def _render_template(options) + view_context.render_template(options) + end + + # The list of view paths for this controller. See ActionView::ViewPathSet for + # more details about writing custom view paths. + def view_paths + _view_paths + end + + # Return a string representation of a Rack-compatible response body. + def self.body_to_s(body) + if body.respond_to?(:to_str) + body + else + strings = [] + body.each { |part| strings << part.to_s } + body.close if body.respond_to?(:close) + strings.join + end + end + + private + # Take in a set of options and determine the template to render + # + # ==== Options + # _template:: If this is provided, the search is over + # _template_name<#to_s>:: The name of the template to look up. Otherwise, + # use the current action name. + # _prefix:: The prefix to look inside of. In a file system, this corresponds + # to a directory. + # _partial:: Whether or not the file to look up is a partial + def _determine_template(options) + name = (options[:_template_name] || action_name).to_s + + options[:_template] ||= view_paths.find_by_parts( + name, { :formats => formats }, options[:_prefix], options[:_partial] + ) + end + + module ClassMethods + # Append a path to the list of view paths for this controller. + # + # ==== Parameters + # path:: If a String is provided, it gets converted into + # the default view path. You may also provide a custom view path + # (see ActionView::ViewPathSet for more information) + def append_view_path(path) + self.view_paths << path + end + + # Prepend a path to the list of view paths for this controller. + # + # ==== Parameters + # path:: If a String is provided, it gets converted into + # the default view path. You may also provide a custom view path + # (see ActionView::ViewPathSet for more information) + def prepend_view_path(path) + self.view_paths.unshift(path) + end + + # A list of all of the default view paths for this controller. + def view_paths + self._view_paths + end + + # Set the view paths. + # + # ==== Parameters + # paths:: If a ViewPathSet is provided, use that; + # otherwise, process the parameter into a ViewPathSet. + def view_paths=(paths) + self._view_paths = paths.is_a?(ActionView::PathSet) ? + paths : ActionView::Base.process_view_paths(paths) + end + end + end +end diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 22c2c4f499..8343a87936 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -8,8 +8,8 @@ module ActionController autoload :Rails2Compatibility, "action_controller/metal/compatibility" autoload :Redirector, "action_controller/metal/redirector" autoload :Renderer, "action_controller/metal/renderer" + autoload :RenderingController, "action_controller/metal/rendering_controller" autoload :RenderOptions, "action_controller/metal/render_options" - autoload :Renderers, "action_controller/metal/render_options" autoload :Rescue, "action_controller/metal/rescuable" autoload :Testing, "action_controller/metal/testing" autoload :UrlFor, "action_controller/metal/url_for" @@ -69,4 +69,4 @@ require 'active_support/core_ext/load_error' require 'active_support/core_ext/module/attr_internal' require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/name_error' -require 'active_support/inflector' \ No newline at end of file +require 'active_support/inflector' diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 9d9f735e27..40f4802b74 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -10,8 +10,8 @@ module ActionController include ActionController::HideActions include ActionController::UrlFor include ActionController::Redirector - include ActionController::Renderer - include ActionController::Renderers::All + include ActionController::RenderingController + include ActionController::RenderOptions::All include ActionController::Layouts include ActionController::ConditionalGet include ActionController::RackConvenience diff --git a/actionpack/lib/action_controller/metal/layouts.rb b/actionpack/lib/action_controller/metal/layouts.rb index 365351b421..cac529b1ae 100644 --- a/actionpack/lib/action_controller/metal/layouts.rb +++ b/actionpack/lib/action_controller/metal/layouts.rb @@ -158,7 +158,7 @@ module ActionController module Layouts extend ActiveSupport::Concern - include ActionController::Renderer + include ActionController::RenderingController include AbstractController::Layouts module ClassMethods diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index d43f940774..d823dd424a 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -1,146 +1,4 @@ module ActionController #:nodoc: - - # Presenter is responsible to expose a resource for different mime requests, - # usually depending on the HTTP verb. The presenter is triggered when - # respond_with is called. The simplest case to study is a GET request: - # - # class PeopleController < ApplicationController - # respond_to :html, :xml, :json - # - # def index - # @people = Person.find(:all) - # respond_with(@people) - # end - # end - # - # When a request comes, for example with format :xml, three steps happen: - # - # 1) respond_with searches for a template at people/index.xml; - # - # 2) if the template is not available, it will create a presenter, passing - # the controller and the resource, and invoke :to_xml on it; - # - # 3) if the presenter does not respond_to :to_xml, call to_format on it. - # - # === Builtin HTTP verb semantics - # - # Rails default presenter holds semantics for each HTTP verb. Depending on the - # content type, verb and the resource status, it will behave differently. - # - # Using Rails default presenter, a POST request could be written as: - # - # def create - # @user = User.new(params[:user]) - # flash[:notice] = 'User was successfully created.' if @user.save - # respond_with(@user) - # end - # - # Which is exactly the same as: - # - # def create - # @user = User.new(params[:user]) - # - # respond_to do |format| - # if @user.save - # flash[:notice] = 'User was successfully created.' - # format.html { redirect_to(@user) } - # format.xml { render :xml => @user, :status => :created, :location => @user } - # else - # format.html { render :action => "new" } - # format.xml { render :xml => @user.errors, :status => :unprocessable_entity } - # end - # end - # end - # - # The same happens for PUT and DELETE requests. By default, it accepts just - # :location as parameter, which is used as redirect destination, in both - # POST, PUT, DELETE requests for HTML mime, as in the example below: - # - # def destroy - # @person = Person.find(params[:id]) - # @person.destroy - # respond_with(@person, :location => root_url) - # end - # - # === Nested resources - # - # You can given nested resource as you do in form_for and polymorphic_url. - # Consider the project has many tasks example. The create action for - # TasksController would be like: - # - # def create - # @project = Project.find(params[:project_id]) - # @task = @project.comments.build(params[:task]) - # flash[:notice] = 'Task was successfully created.' if @task.save - # respond_with([@project, @task]) - # end - # - # Given a nested resource, you ensure that the presenter will redirect to - # project_task_url instead of task_url. - # - # Namespaced and singleton resources requires a symbol to be given, as in - # polymorphic urls. If a project has one manager which has many tasks, it - # should be invoked as: - # - # respond_with([@project, :manager, @task]) - # - # Check polymorphic_url documentation for more examples. - # - class Presenter - attr_reader :controller, :request, :format, :resource, :resource_location, :options - - def initialize(controller, resource, options) - @controller = controller - @request = controller.request - @format = controller.formats.first - @resource = resource.is_a?(Array) ? resource.last : resource - @resource_location = options[:location] || resource - @options = options - end - - delegate :head, :render, :redirect_to, :to => :controller - delegate :get?, :post?, :put?, :delete?, :to => :request - - # Undefine :to_json since it's defined on Object - undef_method :to_json - - def to_html - if get? - render - elsif has_errors? - render :action => default_action - else - redirect_to resource_location - end - end - - def to_format - return render unless resourceful? - - if get? - render format => resource - elsif has_errors? - render format => resource.errors, :status => :unprocessable_entity - elsif post? - render format => resource, :status => :created, :location => resource_location - else - head :ok - end - end - - def resourceful? - resource.respond_to?(:"to_#{format}") - end - - def has_errors? - resource.respond_to?(:errors) && !resource.errors.empty? - end - - def default_action - request.post? ? :new : :edit - end - end - module MimeResponds #:nodoc: extend ActiveSupport::Concern @@ -339,10 +197,10 @@ module ActionController #:nodoc: end end - # respond_with wraps a resource around a presenter for default representation. + # respond_with wraps a resource around a renderer for default representation. # First it invokes respond_to, if a response cannot be found (ie. no block # for the request was given and template was not available), it instantiates - # an ActionController::Presenter with the controller and resource. + # an ActionController::Renderer with the controller and resource. # # ==== Example # @@ -363,19 +221,19 @@ module ActionController #:nodoc: # end # end # - # All options given to respond_with are sent to the underlying presenter. + # All options given to respond_with are sent to the underlying renderer, + # except for the option :renderer itself. Since the renderer interface + # is quite simple (it just needs to respond to call), you can even give + # a proc to it. # def respond_with(resource, options={}, &block) respond_to(&block) rescue ActionView::MissingTemplate - presenter = ActionController::Presenter.new(self, resource, options) - format_method = :"to_#{self.formats.first}" + (options.delete(:renderer) || renderer).call(self, resource, options) + end - if presenter.respond_to?(format_method) - presenter.send(format_method) - else - presenter.to_format - end + def renderer + ActionController::Renderer end protected diff --git a/actionpack/lib/action_controller/metal/render_options.rb b/actionpack/lib/action_controller/metal/render_options.rb index c6a965472f..0d69ca10df 100644 --- a/actionpack/lib/action_controller/metal/render_options.rb +++ b/actionpack/lib/action_controller/metal/render_options.rb @@ -47,7 +47,7 @@ module ActionController end end - module Renderers + module RenderOptions module Json extend RenderOption register_renderer :json @@ -94,10 +94,10 @@ module ActionController module All extend ActiveSupport::Concern - include ActionController::Renderers::Json - include ActionController::Renderers::Js - include ActionController::Renderers::Xml - include ActionController::Renderers::RJS + include ActionController::RenderOptions::Json + include ActionController::RenderOptions::Js + include ActionController::RenderOptions::Xml + include ActionController::RenderOptions::RJS end end end diff --git a/actionpack/lib/action_controller/metal/renderer.rb b/actionpack/lib/action_controller/metal/renderer.rb index 31ba7e582a..39ab2407aa 100644 --- a/actionpack/lib/action_controller/metal/renderer.rb +++ b/actionpack/lib/action_controller/metal/renderer.rb @@ -1,66 +1,181 @@ -module ActionController - module Renderer - extend ActiveSupport::Concern +module ActionController #:nodoc: + # Renderer is responsible to expose a resource for different mime requests, + # usually depending on the HTTP verb. The renderer is triggered when + # respond_with is called. The simplest case to study is a GET request: + # + # class PeopleController < ApplicationController + # respond_to :html, :xml, :json + # + # def index + # @people = Person.find(:all) + # respond_with(@people) + # end + # end + # + # When a request comes, for example with format :xml, three steps happen: + # + # 1) respond_with searches for a template at people/index.xml; + # + # 2) if the template is not available, it will create a renderer, passing + # the controller and the resource and invoke :to_xml on it; + # + # 3) if the renderer does not respond_to :to_xml, call to_format on it. + # + # === Builtin HTTP verb semantics + # + # Rails default renderer holds semantics for each HTTP verb. Depending on the + # content type, verb and the resource status, it will behave differently. + # + # Using Rails default renderer, a POST request for creating an object could + # be written as: + # + # def create + # @user = User.new(params[:user]) + # flash[:notice] = 'User was successfully created.' if @user.save + # respond_with(@user) + # end + # + # Which is exactly the same as: + # + # def create + # @user = User.new(params[:user]) + # + # respond_to do |format| + # if @user.save + # flash[:notice] = 'User was successfully created.' + # format.html { redirect_to(@user) } + # format.xml { render :xml => @user, :status => :created, :location => @user } + # else + # format.html { render :action => "new" } + # format.xml { render :xml => @user.errors, :status => :unprocessable_entity } + # end + # end + # end + # + # The same happens for PUT and DELETE requests. + # + # === Nested resources + # + # You can given nested resource as you do in form_for and polymorphic_url. + # Consider the project has many tasks example. The create action for + # TasksController would be like: + # + # def create + # @project = Project.find(params[:project_id]) + # @task = @project.comments.build(params[:task]) + # flash[:notice] = 'Task was successfully created.' if @task.save + # respond_with([@project, @task]) + # end + # + # Giving an array of resources, you ensure that the renderer will redirect to + # project_task_url instead of task_url. + # + # Namespaced and singleton resources requires a symbol to be given, as in + # polymorphic urls. If a project has one manager which has many tasks, it + # should be invoked as: + # + # respond_with([@project, :manager, @task]) + # + # Check polymorphic_url documentation for more examples. + # + class Renderer + attr_reader :controller, :request, :format, :resource, :resource_location, :options - include AbstractController::Renderer + def initialize(controller, resource, options={}) + @controller = controller + @request = controller.request + @format = controller.formats.first + @resource = resource.is_a?(Array) ? resource.last : resource + @resource_location = options[:location] || resource + @options = options + end + + delegate :head, :render, :redirect_to, :to => :controller + delegate :get?, :post?, :put?, :delete?, :to => :request - def process_action(*) - self.formats = request.formats.map {|x| x.to_sym} - super + # Undefine :to_json since it's defined on Object + undef_method :to_json + + # Initializes a new renderer an invoke the proper format. If the format is + # not defined, call to_format. + # + def self.call(*args) + renderer = new(*args) + method = :"to_#{renderer.format}" + renderer.respond_to?(method) ? renderer.send(method) : renderer.to_format end - def render(options) - super - 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.to_s - response_body + # HTML format does not render the resource, it always attempt to render a + # template. + # + def to_html + if get? + render + elsif has_errors? + render :action => default_action + else + redirect_to resource_location + end end - def render_to_body(options) - _process_options(options) + # All others formats try to render the resource given instead. For this + # purpose a helper called display as a shortcut to render a resource with + # the current format. + # + def to_format + return render unless resourceful? - if options.key?(:partial) - options[:partial] = action_name if options[:partial] == true - options[:_details] = {:formats => formats} + if get? + display resource + elsif has_errors? + display resource.errors, :status => :unprocessable_entity + elsif post? + display resource, :status => :created, :location => resource_location + else + head :ok end - - super end - private - def _prefix - controller_path - end + protected - def _determine_template(options) - if options.key?(:text) - options[:_template] = ActionView::TextTemplate.new(options[:text], formats.first) - elsif options.key?(:inline) - handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb") - template = ActionView::Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {}) - options[:_template] = template - elsif options.key?(:template) - options[:_template_name] = options[:template] - elsif options.key?(:file) - options[:_template_name] = options[:file] - elsif !options.key?(:partial) - options[:_template_name] = (options[:action] || action_name).to_s - options[:_prefix] = _prefix - end + # Checks whether the resource responds to the current format or not. + # + def resourceful? + resource.respond_to?(:"to_#{format}") + end - super - end + # display is just a shortcut to render a resource with the current format. + # + # display @user, :status => :ok + # + # For xml request is equivalent to: + # + # render :xml => @user, :status => :ok + # + # Options sent by the user are also used: + # + # respond_with(@user, :status => :created) + # display(@user, :status => :ok) + # + # Results in: + # + # render :xml => @user, :status => :created + # + def display(resource, given_options={}) + render given_options.merge!(options).merge!(format => resource) + end - def _render_partial(partial, options) - end + # Check if the resource has errors or not. + # + def has_errors? + resource.respond_to?(:errors) && !resource.errors.empty? + end - def _process_options(options) - status, content_type, location = options.values_at(:status, :content_type, :location) - self.status = status if status - self.content_type = content_type if content_type - self.headers["Location"] = url_for(location) if location - end + # By default, render the :edit action for html requests with failure, unless + # the verb is post. + # + def default_action + request.post? ? :new : :edit + end end end diff --git a/actionpack/lib/action_controller/metal/rendering_controller.rb b/actionpack/lib/action_controller/metal/rendering_controller.rb new file mode 100644 index 0000000000..c8922290f3 --- /dev/null +++ b/actionpack/lib/action_controller/metal/rendering_controller.rb @@ -0,0 +1,66 @@ +module ActionController + module RenderingController + extend ActiveSupport::Concern + + include AbstractController::RenderingController + + def process_action(*) + self.formats = request.formats.map {|x| x.to_sym} + super + end + + def render(options) + super + 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.to_s + response_body + end + + def render_to_body(options) + _process_options(options) + + if options.key?(:partial) + options[:partial] = action_name if options[:partial] == true + options[:_details] = {:formats => formats} + end + + super + end + + private + def _prefix + controller_path + end + + def _determine_template(options) + if options.key?(:text) + options[:_template] = ActionView::TextTemplate.new(options[:text], formats.first) + elsif options.key?(:inline) + handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb") + template = ActionView::Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {}) + options[:_template] = template + elsif options.key?(:template) + options[:_template_name] = options[:template] + elsif options.key?(:file) + options[:_template_name] = options[:file] + elsif !options.key?(:partial) + options[:_template_name] = (options[:action] || action_name).to_s + options[:_prefix] = _prefix + end + + super + end + + def _render_partial(partial, options) + end + + def _process_options(options) + status, content_type, location = options.values_at(:status, :content_type, :location) + self.status = status if status + self.content_type = content_type if content_type + self.headers["Location"] = url_for(location) if location + end + end +end diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index f0317c6e99..57318e8747 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -6,7 +6,7 @@ module ActionController #:nodoc: module Streaming extend ActiveSupport::Concern - include ActionController::Renderer + include ActionController::RenderingController DEFAULT_SEND_FILE_OPTIONS = { :type => 'application/octet-stream'.freeze, diff --git a/actionpack/lib/action_controller/metal/verification.rb b/actionpack/lib/action_controller/metal/verification.rb index 951ae1bee1..d3d78e3749 100644 --- a/actionpack/lib/action_controller/metal/verification.rb +++ b/actionpack/lib/action_controller/metal/verification.rb @@ -2,7 +2,7 @@ module ActionController #:nodoc: module Verification #:nodoc: extend ActiveSupport::Concern - include AbstractController::Callbacks, Session, Flash, Renderer + include AbstractController::Callbacks, Session, Flash, RenderingController # This module provides a class-level method for specifying that certain # actions are guarded against being called without certain prerequisites @@ -127,4 +127,4 @@ module ActionController #:nodoc: end end end -end \ No newline at end of file +end diff --git a/actionpack/test/abstract_controller/abstract_controller_test.rb b/actionpack/test/abstract_controller/abstract_controller_test.rb index 56ec6a6a31..10b97f4eb4 100644 --- a/actionpack/test/abstract_controller/abstract_controller_test.rb +++ b/actionpack/test/abstract_controller/abstract_controller_test.rb @@ -27,7 +27,7 @@ module AbstractController # Test Render mixin # ==== class RenderingController < AbstractController::Base - include Renderer + include ::AbstractController::RenderingController def _prefix() end @@ -65,8 +65,8 @@ module AbstractController self.response_body = render_to_string :_template_name => "naked_render.erb" end end - - class TestRenderer < ActiveSupport::TestCase + + class TestRenderingController < ActiveSupport::TestCase test "rendering templates works" do result = Me2.new.process(:index) assert_equal "Hello from index.erb", result.response_body diff --git a/actionpack/test/abstract_controller/helper_test.rb b/actionpack/test/abstract_controller/helper_test.rb index 0a2535f834..cd6d4ee90e 100644 --- a/actionpack/test/abstract_controller/helper_test.rb +++ b/actionpack/test/abstract_controller/helper_test.rb @@ -4,7 +4,7 @@ module AbstractController module Testing class ControllerWithHelpers < AbstractController::Base - include Renderer + include RenderingController include Helpers def render(string) @@ -40,4 +40,4 @@ module AbstractController end end -end \ No newline at end of file +end diff --git a/actionpack/test/abstract_controller/layouts_test.rb b/actionpack/test/abstract_controller/layouts_test.rb index b28df7743f..42f73faa61 100644 --- a/actionpack/test/abstract_controller/layouts_test.rb +++ b/actionpack/test/abstract_controller/layouts_test.rb @@ -6,7 +6,7 @@ module AbstractControllerTests # Base controller for these tests class Base < AbstractController::Base - include AbstractController::Renderer + include AbstractController::RenderingController include AbstractController::Layouts self.view_paths = [ActionView::FixtureResolver.new( diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 157e837503..00ad90ff68 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -501,8 +501,13 @@ class RespondWithController < ActionController::Base respond_with([Quiz::Store.new("developer?", 11), Customer.new("david", 13)]) end - def using_resource_with_location - respond_with(Customer.new("david", 13), :location => "http://test.host/") + def using_resource_with_status_and_location + respond_with(Customer.new("david", 13), :location => "http://test.host/", :status => :created) + end + + def using_resource_with_renderer + renderer = proc { |c, r, o| c.render :text => "Resource name is #{r.name}" } + respond_with(Customer.new("david", 13), :renderer => renderer) end protected @@ -727,11 +732,20 @@ class RespondWithControllerTest < ActionController::TestCase end end - def test_using_resource_with_location + def test_using_resource_with_status_and_location @request.accept = "text/html" - post :using_resource_with_location + post :using_resource_with_status_and_location assert @response.redirect? assert_equal "http://test.host/", @response.location + + @request.accept = "application/xml" + get :using_resource_with_status_and_location + assert_equal 201, @response.status + end + + def test_using_resource_with_renderer + get :using_resource_with_renderer + assert_equal "Resource name is david", @response.body end def test_not_acceptable -- cgit v1.2.3