Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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

…ts API.
  • Loading branch information...
commit aed135d3e261cbee153a35fcfbeb47e2e02b12e4 1 parent 1fd65c8
@josevalim josevalim authored
View
4 actionpack/examples/very_simple.rb
@@ -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
View
20 actionpack/lib/abstract_controller.rb
@@ -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
View
2  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 }
View
2  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 }
View
4 ...npack/lib/abstract_controller/renderer.rb → ...stract_controller/rendering_controller.rb
@@ -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.
View
4 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 @@ 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'
View
4 actionpack/lib/action_controller/base.rb
@@ -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
View
2  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
View
162 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 @@ 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
View
10 actionpack/lib/action_controller/metal/render_options.rb
@@ -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
View
215 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
View
66 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
View
2  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,
View
4 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 @@ def apply_remaining_actions(options) # :nodoc:
end
end
end
-end
+end
View
6 actionpack/test/abstract_controller/abstract_controller_test.rb
@@ -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
View
4 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 @@ def test_helpers
end
end
-end
+end
View
2  actionpack/test/abstract_controller/layouts_test.rb
@@ -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(
View
22 actionpack/test/controller/mime_responds_test.rb
@@ -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.