Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Renamed presenter to renderer, added some documentation and defined i…

…ts API.
commit aed135d3e261cbee153a35fcfbeb47e2e02b12e4 1 parent 1fd65c8
José Valim josevalim authored
4 actionpack/examples/very_simple.rb
View
@@ -6,7 +6,7 @@
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 @@ def set_name
map("/kaigi/alt") { run Kaigi.action(:alt) }
end.to_app
-Rack::Handler::Mongrel.run app, :Port => 3000
+Rack::Handler::Mongrel.run app, :Port => 3000
20 actionpack/lib/abstract_controller.rb
View
@@ -2,15 +2,15 @@
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
2  actionpack/lib/abstract_controller/helpers.rb
View
@@ -2,7 +2,7 @@ module AbstractController
module Helpers
extend ActiveSupport::Concern
- include Renderer
+ include RenderingController
included do
extlib_inheritable_accessor(:_helpers) { Module.new }
2  actionpack/lib/abstract_controller/layouts.rb
View
@@ -2,7 +2,7 @@ module AbstractController
module Layouts
extend ActiveSupport::Concern
- include Renderer
+ include RenderingController
included do
extlib_inheritable_accessor(:_layout_conditions) { Hash.new }
4 actionpack/lib/abstract_controller/renderer.rb → ...k/lib/abstract_controller/rendering_controller.rb
View
@@ -1,7 +1,7 @@
require "abstract_controller/logger"
module AbstractController
- module Renderer
+ module RenderingController
extend ActiveSupport::Concern
include AbstractController::Logger
@@ -67,7 +67,7 @@ def render_to_body(options = {})
#
# :api: plugin
def render_to_string(options = {})
- AbstractController::Renderer.body_to_s(render_to_body(options))
+ AbstractController::RenderingController.body_to_s(render_to_body(options))
end
# Renders the template from an object.
4 actionpack/lib/action_controller.rb
View
@@ -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 @@ module ActionController
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'
+require 'active_support/inflector'
4 actionpack/lib/action_controller/base.rb
View
@@ -10,8 +10,8 @@ class Base < Metal
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
2  actionpack/lib/action_controller/metal/layouts.rb
View
@@ -158,7 +158,7 @@ module ActionController
module Layouts
extend ActiveSupport::Concern
- include ActionController::Renderer
+ include ActionController::RenderingController
include AbstractController::Layouts
module ClassMethods
162 actionpack/lib/action_controller/metal/mime_responds.rb
View
@@ -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 @@ def respond_to(*mimes, &block)
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 @@ def respond_to(*mimes, &block)
# 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
10 actionpack/lib/action_controller/metal/render_options.rb
View
@@ -47,7 +47,7 @@ def base.register_renderer(name)
end
end
- module Renderers
+ module RenderOptions
module Json
extend RenderOption
register_renderer :json
@@ -94,10 +94,10 @@ def render_update(proc, options)
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
215 actionpack/lib/action_controller/metal/renderer.rb
View
@@ -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
66 actionpack/lib/action_controller/metal/rendering_controller.rb
View
@@ -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
2  actionpack/lib/action_controller/metal/streaming.rb
View
@@ -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,
4 actionpack/lib/action_controller/metal/verification.rb
View
@@ -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 @@ def apply_remaining_actions(options) # :nodoc:
end
end
end
-end
+end
6 actionpack/test/abstract_controller/abstract_controller_test.rb
View
@@ -27,7 +27,7 @@ class TestBasic < ActiveSupport::TestCase
# Test Render mixin
# ====
class RenderingController < AbstractController::Base
- include Renderer
+ include ::AbstractController::RenderingController
def _prefix() end
@@ -65,8 +65,8 @@ def rendering_to_string
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
4 actionpack/test/abstract_controller/helper_test.rb
View
@@ -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 @@ def test_helpers
end
end
-end
+end
2  actionpack/test/abstract_controller/layouts_test.rb
View
@@ -6,7 +6,7 @@ module Layouts
# Base controller for these tests
class Base < AbstractController::Base
- include AbstractController::Renderer
+ include AbstractController::RenderingController
include AbstractController::Layouts
self.view_paths = [ActionView::FixtureResolver.new(
22 actionpack/test/controller/mime_responds_test.rb
View
@@ -501,8 +501,13 @@ def using_resource_with_parent
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 @@ def test_no_double_render_is_raised
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
Please sign in to comment.
Something went wrong with that request. Please try again.