aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/CHANGELOG.md29
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb4
-rw-r--r--actionpack/lib/action_controller/metal/rack_delegation.rb4
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb45
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb13
-rw-r--r--actionpack/test/controller/new_base/render_body_test.rb175
-rw-r--r--actionpack/test/controller/new_base/render_html_test.rb190
-rw-r--r--actionpack/test/controller/new_base/render_plain_test.rb168
-rw-r--r--actionpack/test/controller/new_base/render_text_test.rb36
-rw-r--r--actionpack/test/dispatch/response_test.rb8
-rw-r--r--actionview/CHANGELOG.md5
-rw-r--r--actionview/lib/action_view/helpers/rendering_helper.rb7
-rw-r--r--actionview/lib/action_view/layouts.rb2
-rw-r--r--actionview/lib/action_view/renderer/template_renderer.rb10
-rw-r--r--actionview/lib/action_view/rendering.rb7
-rw-r--r--actionview/lib/action_view/template.rb1
-rw-r--r--actionview/lib/action_view/template/html.rb34
-rw-r--r--actionview/test/template/render_test.rb2
-rw-r--r--guides/source/action_controller_overview.md2
-rw-r--r--guides/source/getting_started.md2
-rw-r--r--guides/source/layouts_and_rendering.md40
-rw-r--r--guides/source/upgrading_ruby_on_rails.md19
22 files changed, 762 insertions, 41 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 35b0a419d3..b05aa21f95 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,32 @@
+* Introduce `render :html` as an option to render HTML content with a content
+ type of `text/html`. This rendering option calls `ERB::Util.html_escape`
+ internally to escape unsafe HTML string, so you will have to mark your
+ string as html safe if you have any HTML tag in it.
+
+ Please see #12374 for more detail.
+
+ *Prem Sichanugrist*
+
+* Introduce `render :plain` as an option to render content with a content type
+ of `text/plain`. This is the preferred option if you are planning to render
+ a plain text content.
+
+ Please see #12374 for more detail.
+
+ *Prem Sichanugrist*
+
+* Introduce `render :body` as an option for sending a raw content back to
+ browser. Note that this rendering option will unset the default content type
+ and does not include "Content-Type" header back in the response.
+
+ You should only use this option if you are expecting the "Content-Type"
+ header to not be set. More information on "Content-Type" header can be found
+ on RFC 2616, section 7.2.1.
+
+ Please see #12374 for more detail.
+
+ *Prem Sichanugrist*
+
* Set stream status to 500 (or 400 on BadRequest) when an error is thrown
before commiting.
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb
index f24b03ad16..349bbf4ee7 100644
--- a/actionpack/lib/abstract_controller/rendering.rb
+++ b/actionpack/lib/abstract_controller/rendering.rb
@@ -23,7 +23,7 @@ module AbstractController
def render(*args, &block)
options = _normalize_render(*args, &block)
self.response_body = render_to_body(options)
- _process_format(rendered_format) if rendered_format
+ _process_format(rendered_format, options) if rendered_format
self.response_body
end
@@ -98,7 +98,7 @@ module AbstractController
# Process the rendered format.
# :api: private
- def _process_format(format)
+ def _process_format(format, options = {})
end
# Normalize args and options.
diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb
index bdf6e88699..e1bee9e60c 100644
--- a/actionpack/lib/action_controller/metal/rack_delegation.rb
+++ b/actionpack/lib/action_controller/metal/rack_delegation.rb
@@ -5,8 +5,8 @@ module ActionController
module RackDelegation
extend ActiveSupport::Concern
- delegate :headers, :status=, :location=, :content_type=,
- :status, :location, :content_type, :to => "@_response"
+ delegate :headers, :status=, :location=, :content_type=, :no_content_type=,
+ :status, :location, :content_type, :no_content_type, :to => "@_response"
def dispatch(action, request)
set_response!(request)
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 5c48b4ab98..3c4ef596c7 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -2,6 +2,8 @@ module ActionController
module Rendering
extend ActiveSupport::Concern
+ RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html]
+
# Before processing, set the request formats in current controller formats.
def process_action(*) #:nodoc:
self.formats = request.formats.map(&:ref).compact
@@ -27,14 +29,29 @@ module ActionController
end
def render_to_body(options = {})
- super || options[:text].presence || ' '
+ super || _render_in_priorities(options) || ' '
end
private
- def _process_format(format)
+ def _render_in_priorities(options)
+ RENDER_FORMATS_IN_PRIORITY.each do |format|
+ return options[format] if options.key?(format)
+ end
+
+ nil
+ end
+
+ def _process_format(format, options = {})
super
- self.content_type ||= format.to_s
+
+ if options[:body]
+ self.headers.delete "Content-Type"
+ elsif options[:plain]
+ self.content_type = Mime::TEXT
+ else
+ self.content_type ||= format.to_s
+ end
end
# Normalize arguments by catching blocks and setting them on :update.
@@ -46,12 +63,14 @@ module ActionController
# Normalize both text and status options.
def _normalize_options(options) #:nodoc:
- if options.key?(:text) && options[:text].respond_to?(:to_text)
- options[:text] = options[:text].to_text
+ _normalize_text(options)
+
+ if options[:html]
+ options[:html] = ERB::Util.html_escape(options[:html])
end
- if options.delete(:nothing) || (options.key?(:text) && options[:text].nil?)
- options[:text] = " "
+ if options.delete(:nothing) || _any_render_format_is_nil?(options)
+ options[:body] = " "
end
if options[:status]
@@ -61,6 +80,18 @@ module ActionController
super
end
+ def _normalize_text(options)
+ RENDER_FORMATS_IN_PRIORITY.each do |format|
+ if options.key?(format) && options[format].respond_to?(:to_text)
+ options[format] = options[format].to_text
+ end
+ end
+ end
+
+ def _any_render_format_is_nil?(options)
+ RENDER_FORMATS_IN_PRIORITY.any? { |format| options.key?(format) && options[format].nil? }
+ end
+
# Process controller specific options, as status, content-type and location.
def _process_options(options) #:nodoc:
status, content_type, location = options.values_at(:status, :content_type, :location)
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 2c6bcf7b7b..f14ca1ea44 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -63,6 +63,8 @@ module ActionDispatch # :nodoc:
# content you're giving them, so we need to send that along.
attr_accessor :charset
+ attr_accessor :no_content_type # :nodoc:
+
CONTENT_TYPE = "Content-Type".freeze
SET_COOKIE = "Set-Cookie".freeze
LOCATION = "Location".freeze
@@ -303,8 +305,17 @@ module ActionDispatch # :nodoc:
!@sending_file && @charset != false
end
+ def remove_content_type!
+ headers.delete CONTENT_TYPE
+ end
+
def rack_response(status, header)
- assign_default_content_type_and_charset!(header)
+ if no_content_type
+ remove_content_type!
+ else
+ assign_default_content_type_and_charset!(header)
+ end
+
handle_conditional_get!
header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join)
diff --git a/actionpack/test/controller/new_base/render_body_test.rb b/actionpack/test/controller/new_base/render_body_test.rb
new file mode 100644
index 0000000000..a7e4f87bd9
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_body_test.rb
@@ -0,0 +1,175 @@
+require 'abstract_unit'
+
+module RenderBody
+ class MinimalController < ActionController::Metal
+ include AbstractController::Rendering
+ include ActionController::Rendering
+
+ def index
+ render body: "Hello World!"
+ end
+ end
+
+ class SimpleController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new]
+
+ def index
+ render body: "hello david"
+ end
+ end
+
+ class WithLayoutController < ::ApplicationController
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "layouts/application.erb" => "<%= yield %>, I'm here!",
+ "layouts/greetings.erb" => "<%= yield %>, I wish thee well.",
+ "layouts/ivar.erb" => "<%= yield %>, <%= @ivar %>"
+ )]
+
+ def index
+ render body: "hello david"
+ end
+
+ def custom_code
+ render body: "hello world", status: 404
+ end
+
+ def with_custom_code_as_string
+ render body: "hello world", status: "404 Not Found"
+ end
+
+ def with_nil
+ render body: nil
+ end
+
+ def with_nil_and_status
+ render body: nil, status: 403
+ end
+
+ def with_false
+ render body: false
+ end
+
+ def with_layout_true
+ render body: "hello world", layout: true
+ end
+
+ def with_layout_false
+ render body: "hello world", layout: false
+ end
+
+ def with_layout_nil
+ render body: "hello world", layout: nil
+ end
+
+ def with_custom_layout
+ render body: "hello world", layout: "greetings"
+ end
+
+ def with_ivar_in_layout
+ @ivar = "hello world"
+ render body: "hello world", layout: "ivar"
+ end
+ end
+
+ class RenderBodyTest < Rack::TestCase
+ test "rendering body from a minimal controller" do
+ get "/render_body/minimal/index"
+ assert_body "Hello World!"
+ assert_status 200
+ end
+
+ test "rendering body from an action with default options renders the body with the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_body/simple"
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering body from an action with default options renders the body without the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_body/with_layout"
+
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering body, while also providing a custom status code" do
+ get "/render_body/with_layout/custom_code"
+
+ assert_body "hello world"
+ assert_status 404
+ end
+
+ test "rendering body with nil returns an empty body padded for Safari" do
+ get "/render_body/with_layout/with_nil"
+
+ assert_body " "
+ assert_status 200
+ end
+
+ test "Rendering body with nil and custom status code returns an empty body padded for Safari and the status" do
+ get "/render_body/with_layout/with_nil_and_status"
+
+ assert_body " "
+ assert_status 403
+ end
+
+ test "rendering body with false returns the string 'false'" do
+ get "/render_body/with_layout/with_false"
+
+ assert_body "false"
+ assert_status 200
+ end
+
+ test "rendering body with layout: true" do
+ get "/render_body/with_layout/with_layout_true"
+
+ assert_body "hello world, I'm here!"
+ assert_status 200
+ end
+
+ test "rendering body with layout: 'greetings'" do
+ get "/render_body/with_layout/with_custom_layout"
+
+ assert_body "hello world, I wish thee well."
+ assert_status 200
+ end
+
+ test "rendering body with layout: false" do
+ get "/render_body/with_layout/with_layout_false"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering body with layout: nil" do
+ get "/render_body/with_layout/with_layout_nil"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering from minimal controller returns response with no content type" do
+ get "/render_body/minimal/index"
+
+ assert_header_no_content_type
+ end
+
+ test "rendering from normal controller returns response with no content type" do
+ get "/render_body/simple/index"
+
+ assert_header_no_content_type
+ end
+
+ def assert_header_no_content_type
+ assert_not response.headers.has_key?("Content-Type"),
+ %(Expect response not to have Content-Type header, got "#{response.headers["Content-Type"]}")
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_html_test.rb b/actionpack/test/controller/new_base/render_html_test.rb
new file mode 100644
index 0000000000..bfe0271df7
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_html_test.rb
@@ -0,0 +1,190 @@
+require 'abstract_unit'
+
+module RenderHtml
+ class MinimalController < ActionController::Metal
+ include AbstractController::Rendering
+ include ActionController::Rendering
+
+ def index
+ render html: "Hello World!"
+ end
+ end
+
+ class SimpleController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new]
+
+ def index
+ render html: "hello david"
+ end
+ end
+
+ class WithLayoutController < ::ApplicationController
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "layouts/application.html.erb" => "<%= yield %>, I'm here!",
+ "layouts/greetings.html.erb" => "<%= yield %>, I wish thee well.",
+ "layouts/ivar.html.erb" => "<%= yield %>, <%= @ivar %>"
+ )]
+
+ def index
+ render html: "hello david"
+ end
+
+ def custom_code
+ render html: "hello world", status: 404
+ end
+
+ def with_custom_code_as_string
+ render html: "hello world", status: "404 Not Found"
+ end
+
+ def with_nil
+ render html: nil
+ end
+
+ def with_nil_and_status
+ render html: nil, status: 403
+ end
+
+ def with_false
+ render html: false
+ end
+
+ def with_layout_true
+ render html: "hello world", layout: true
+ end
+
+ def with_layout_false
+ render html: "hello world", layout: false
+ end
+
+ def with_layout_nil
+ render html: "hello world", layout: nil
+ end
+
+ def with_custom_layout
+ render html: "hello world", layout: "greetings"
+ end
+
+ def with_ivar_in_layout
+ @ivar = "hello world"
+ render html: "hello world", layout: "ivar"
+ end
+
+ def with_unsafe_html_tag
+ render html: "<p>hello world</p>", layout: nil
+ end
+
+ def with_safe_html_tag
+ render html: "<p>hello world</p>".html_safe, layout: nil
+ end
+ end
+
+ class RenderHtmlTest < Rack::TestCase
+ test "rendering text from a minimal controller" do
+ get "/render_html/minimal/index"
+ assert_body "Hello World!"
+ assert_status 200
+ end
+
+ test "rendering text from an action with default options renders the text with the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_html/simple"
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering text from an action with default options renders the text without the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_html/with_layout"
+
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering text, while also providing a custom status code" do
+ get "/render_html/with_layout/custom_code"
+
+ assert_body "hello world"
+ assert_status 404
+ end
+
+ test "rendering text with nil returns an empty body padded for Safari" do
+ get "/render_html/with_layout/with_nil"
+
+ assert_body " "
+ assert_status 200
+ end
+
+ test "Rendering text with nil and custom status code returns an empty body padded for Safari and the status" do
+ get "/render_html/with_layout/with_nil_and_status"
+
+ assert_body " "
+ assert_status 403
+ end
+
+ test "rendering text with false returns the string 'false'" do
+ get "/render_html/with_layout/with_false"
+
+ assert_body "false"
+ assert_status 200
+ end
+
+ test "rendering text with layout: true" do
+ get "/render_html/with_layout/with_layout_true"
+
+ assert_body "hello world, I'm here!"
+ assert_status 200
+ end
+
+ test "rendering text with layout: 'greetings'" do
+ get "/render_html/with_layout/with_custom_layout"
+
+ assert_body "hello world, I wish thee well."
+ assert_status 200
+ end
+
+ test "rendering text with layout: false" do
+ get "/render_html/with_layout/with_layout_false"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering text with layout: nil" do
+ get "/render_html/with_layout/with_layout_nil"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering html should escape the string if it is not html safe" do
+ get "/render_html/with_layout/with_unsafe_html_tag"
+
+ assert_body "&lt;p&gt;hello world&lt;/p&gt;"
+ assert_status 200
+ end
+
+ test "rendering html should not escape the string if it is html safe" do
+ get "/render_html/with_layout/with_safe_html_tag"
+
+ assert_body "<p>hello world</p>"
+ assert_status 200
+ end
+
+ test "rendering from minimal controller returns response with text/html content type" do
+ get "/render_html/minimal/index"
+ assert_content_type "text/html"
+ end
+
+ test "rendering from normal controller returns response with text/html content type" do
+ get "/render_html/simple/index"
+ assert_content_type "text/html; charset=utf-8"
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_plain_test.rb b/actionpack/test/controller/new_base/render_plain_test.rb
new file mode 100644
index 0000000000..dba2e9f13e
--- /dev/null
+++ b/actionpack/test/controller/new_base/render_plain_test.rb
@@ -0,0 +1,168 @@
+require 'abstract_unit'
+
+module RenderPlain
+ class MinimalController < ActionController::Metal
+ include AbstractController::Rendering
+ include ActionController::Rendering
+
+ def index
+ render plain: "Hello World!"
+ end
+ end
+
+ class SimpleController < ActionController::Base
+ self.view_paths = [ActionView::FixtureResolver.new]
+
+ def index
+ render plain: "hello david"
+ end
+ end
+
+ class WithLayoutController < ::ApplicationController
+ self.view_paths = [ActionView::FixtureResolver.new(
+ "layouts/application.text.erb" => "<%= yield %>, I'm here!",
+ "layouts/greetings.text.erb" => "<%= yield %>, I wish thee well.",
+ "layouts/ivar.text.erb" => "<%= yield %>, <%= @ivar %>"
+ )]
+
+ def index
+ render plain: "hello david"
+ end
+
+ def custom_code
+ render plain: "hello world", status: 404
+ end
+
+ def with_custom_code_as_string
+ render plain: "hello world", status: "404 Not Found"
+ end
+
+ def with_nil
+ render plain: nil
+ end
+
+ def with_nil_and_status
+ render plain: nil, status: 403
+ end
+
+ def with_false
+ render plain: false
+ end
+
+ def with_layout_true
+ render plain: "hello world", layout: true
+ end
+
+ def with_layout_false
+ render plain: "hello world", layout: false
+ end
+
+ def with_layout_nil
+ render plain: "hello world", layout: nil
+ end
+
+ def with_custom_layout
+ render plain: "hello world", layout: "greetings"
+ end
+
+ def with_ivar_in_layout
+ @ivar = "hello world"
+ render plain: "hello world", layout: "ivar"
+ end
+ end
+
+ class RenderPlainTest < Rack::TestCase
+ test "rendering text from a minimal controller" do
+ get "/render_plain/minimal/index"
+ assert_body "Hello World!"
+ assert_status 200
+ end
+
+ test "rendering text from an action with default options renders the text with the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_plain/simple"
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering text from an action with default options renders the text without the layout" do
+ with_routing do |set|
+ set.draw { get ':controller', action: 'index' }
+
+ get "/render_plain/with_layout"
+
+ assert_body "hello david"
+ assert_status 200
+ end
+ end
+
+ test "rendering text, while also providing a custom status code" do
+ get "/render_plain/with_layout/custom_code"
+
+ assert_body "hello world"
+ assert_status 404
+ end
+
+ test "rendering text with nil returns an empty body padded for Safari" do
+ get "/render_plain/with_layout/with_nil"
+
+ assert_body " "
+ assert_status 200
+ end
+
+ test "Rendering text with nil and custom status code returns an empty body padded for Safari and the status" do
+ get "/render_plain/with_layout/with_nil_and_status"
+
+ assert_body " "
+ assert_status 403
+ end
+
+ test "rendering text with false returns the string 'false'" do
+ get "/render_plain/with_layout/with_false"
+
+ assert_body "false"
+ assert_status 200
+ end
+
+ test "rendering text with layout: true" do
+ get "/render_plain/with_layout/with_layout_true"
+
+ assert_body "hello world, I'm here!"
+ assert_status 200
+ end
+
+ test "rendering text with layout: 'greetings'" do
+ get "/render_plain/with_layout/with_custom_layout"
+
+ assert_body "hello world, I wish thee well."
+ assert_status 200
+ end
+
+ test "rendering text with layout: false" do
+ get "/render_plain/with_layout/with_layout_false"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering text with layout: nil" do
+ get "/render_plain/with_layout/with_layout_nil"
+
+ assert_body "hello world"
+ assert_status 200
+ end
+
+ test "rendering from minimal controller returns response with text/plain content type" do
+ get "/render_plain/minimal/index"
+ assert_content_type "text/plain"
+ end
+
+ test "rendering from normal controller returns response with text/plain content type" do
+ get "/render_plain/simple/index"
+ assert_content_type "text/plain; charset=utf-8"
+ end
+ end
+end
diff --git a/actionpack/test/controller/new_base/render_text_test.rb b/actionpack/test/controller/new_base/render_text_test.rb
index 2a253799f3..abb81d7e71 100644
--- a/actionpack/test/controller/new_base/render_text_test.rb
+++ b/actionpack/test/controller/new_base/render_text_test.rb
@@ -14,7 +14,7 @@ module RenderText
self.view_paths = [ActionView::FixtureResolver.new]
def index
- render :text => "hello david"
+ render text: "hello david"
end
end
@@ -26,48 +26,48 @@ module RenderText
)]
def index
- render :text => "hello david"
+ render text: "hello david"
end
def custom_code
- render :text => "hello world", :status => 404
+ render text: "hello world", status: 404
end
def with_custom_code_as_string
- render :text => "hello world", :status => "404 Not Found"
+ render text: "hello world", status: "404 Not Found"
end
def with_nil
- render :text => nil
+ render text: nil
end
def with_nil_and_status
- render :text => nil, :status => 403
+ render text: nil, status: 403
end
def with_false
- render :text => false
+ render text: false
end
def with_layout_true
- render :text => "hello world", :layout => true
+ render text: "hello world", layout: true
end
def with_layout_false
- render :text => "hello world", :layout => false
+ render text: "hello world", layout: false
end
def with_layout_nil
- render :text => "hello world", :layout => nil
+ render text: "hello world", layout: nil
end
def with_custom_layout
- render :text => "hello world", :layout => "greetings"
+ render text: "hello world", layout: "greetings"
end
def with_ivar_in_layout
@ivar = "hello world"
- render :text => "hello world", :layout => "ivar"
+ render text: "hello world", layout: "ivar"
end
end
@@ -80,7 +80,7 @@ module RenderText
test "rendering text from an action with default options renders the text with the layout" do
with_routing do |set|
- set.draw { get ':controller', :action => 'index' }
+ set.draw { get ':controller', action: 'index' }
get "/render_text/simple"
assert_body "hello david"
@@ -90,7 +90,7 @@ module RenderText
test "rendering text from an action with default options renders the text without the layout" do
with_routing do |set|
- set.draw { get ':controller', :action => 'index' }
+ set.draw { get ':controller', action: 'index' }
get "/render_text/with_layout"
@@ -127,28 +127,28 @@ module RenderText
assert_status 200
end
- test "rendering text with :layout => true" do
+ test "rendering text with layout: true" do
get "/render_text/with_layout/with_layout_true"
assert_body "hello world, I'm here!"
assert_status 200
end
- test "rendering text with :layout => 'greetings'" do
+ test "rendering text with layout: 'greetings'" do
get "/render_text/with_layout/with_custom_layout"
assert_body "hello world, I wish thee well."
assert_status 200
end
- test "rendering text with :layout => false" do
+ test "rendering text with layout: false" do
get "/render_text/with_layout/with_layout_false"
assert_body "hello world"
assert_status 200
end
- test "rendering text with :layout => nil" do
+ test "rendering text with layout: nil" do
get "/render_text/with_layout/with_layout_nil"
assert_body "hello world"
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 959a3bc5cd..1360ede3f8 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -235,6 +235,14 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal @response.body, body.each.to_a.join
end
end
+
+ test "does not add default content-type if Content-Type is none" do
+ resp = ActionDispatch::Response.new.tap { |response|
+ response.no_content_type = true
+ }
+
+ assert_not resp.headers.has_key?('Content-Type')
+ end
end
class ResponseIntegrationTest < ActionDispatch::IntegrationTest
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index a0f298a6b1..cc21201903 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,8 @@
+* Added `:plain`, `:html` and `:body` option for `render` method. Please see
+ Action Pack's release note for more detail.
+
+ *Prem Sichanugrist*
+
* Date select helpers accept a format string for the months selector via the
new option `:month_format_string`.
diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb
index 458086de96..15b88bcda6 100644
--- a/actionview/lib/action_view/helpers/rendering_helper.rb
+++ b/actionview/lib/action_view/helpers/rendering_helper.rb
@@ -12,6 +12,13 @@ module ActionView
# * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those.
# * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller.
# * <tt>:text</tt> - Renders the text passed in out.
+ # * <tt>:plain</tt> - Renders the text passed in out. Setting the content
+ # type as <tt>text/plain</tt>.
+ # * <tt>:html</tt> - Renders the html safe string passed in out, otherwise
+ # performs html escape on the string first. Setting the content type as
+ # <tt>text/html</tt>.
+ # * <tt>:body</tt> - Renders the text passed in, and does not set content
+ # type in the response.
#
# If no options hash is passed or :update specified, the default is to render a partial and use the second parameter
# as the locals hash.
diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb
index ffa67649da..9ee05bd816 100644
--- a/actionview/lib/action_view/layouts.rb
+++ b/actionview/lib/action_view/layouts.rb
@@ -420,7 +420,7 @@ module ActionView
end
def _include_layout?(options)
- (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout)
+ (options.keys & [:body, :text, :plain, :html, :inline, :partial]).empty? || options.key?(:layout)
end
end
end
diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb
index 668831dff3..be17097428 100644
--- a/actionview/lib/action_view/renderer/template_renderer.rb
+++ b/actionview/lib/action_view/renderer/template_renderer.rb
@@ -21,8 +21,14 @@ module ActionView
def determine_template(options) #:nodoc:
keys = options.fetch(:locals, {}).keys
- if options.key?(:text)
+ if options.key?(:body)
+ Template::Text.new(options[:body])
+ elsif options.key?(:text)
Template::Text.new(options[:text], formats.first)
+ elsif options.key?(:plain)
+ Template::Text.new(options[:plain])
+ elsif options.key?(:html)
+ Template::HTML.new(options[:html], formats.first)
elsif options.key?(:file)
with_fallbacks { find_template(options[:file], nil, false, keys, @details) }
elsif options.key?(:inline)
@@ -35,7 +41,7 @@ module ActionView
find_template(options[:template], options[:prefixes], false, keys, @details)
end
else
- raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file or :text option."
+ raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :text or :body option."
end
end
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index 7c17220d14..f96587c816 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -100,8 +100,13 @@ module ActionView
end
# Assign the rendered format to lookup context.
- def _process_format(format) #:nodoc:
+ def _process_format(format, options = {}) #:nodoc:
super
+
+ if options[:body]
+ self.no_content_type = true
+ end
+
lookup_context.formats = [format.to_sym]
lookup_context.rendered_format = lookup_context.formats.first
end
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index 9b0619f1aa..961a969b6e 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -90,6 +90,7 @@ module ActionView
eager_autoload do
autoload :Error
autoload :Handlers
+ autoload :HTML
autoload :Text
autoload :Types
end
diff --git a/actionview/lib/action_view/template/html.rb b/actionview/lib/action_view/template/html.rb
new file mode 100644
index 0000000000..282da1a8a2
--- /dev/null
+++ b/actionview/lib/action_view/template/html.rb
@@ -0,0 +1,34 @@
+module ActionView #:nodoc:
+ # = Action View HTML Template
+ class Template
+ class HTML #:nodoc:
+ attr_accessor :type
+
+ def initialize(string, type = nil)
+ @string = string.to_s
+ @type = Types[type] || type if type
+ @type ||= Types[:html]
+ end
+
+ def identifier
+ 'html template'
+ end
+
+ def inspect
+ 'html template'
+ end
+
+ def to_str
+ ERB::Util.h(@string)
+ end
+
+ def render(*args)
+ to_str
+ end
+
+ def formats
+ [@type.to_sym]
+ end
+ end
+ end
+end
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index db5d99755c..ca508abfb8 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -22,7 +22,7 @@ module RenderTestCases
def test_render_without_options
e = assert_raises(ArgumentError) { @view.render() }
- assert_match "You invoked render but did not give any of :partial, :template, :inline, :file or :text option.", e.message
+ assert_match(/You invoked render but did not give any of (.+) option./, e.message)
end
def test_render_file
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index 222d86afe9..5b5f53c9be 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -1088,7 +1088,7 @@ class ApplicationController < ActionController::Base
private
def record_not_found
- render text: "404 Not Found", status: 404
+ render plain: "404 Not Found", status: 404
end
end
```
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 53d2a9b55b..a16b9ac8da 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -608,7 +608,7 @@ look like, change the `create` action to this:
```ruby
def create
- render text: params[:article].inspect
+ render plain: params[:article].inspect
end
```
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index 93e25d619e..66ed6f2e08 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -236,15 +236,34 @@ render inline: "xml.p {'Horrid coding practice!'}", type: :builder
#### Rendering Text
-You can send plain text - with no markup at all - back to the browser by using the `:text` option to `render`:
+You can send plain text - with no markup at all - back to the browser by using
+the `:plain` option to `render`:
```ruby
-render text: "OK"
+render plain: "OK"
```
-TIP: Rendering pure text is most useful when you're responding to Ajax or web service requests that are expecting something other than proper HTML.
+TIP: Rendering pure text is most useful when you're responding to Ajax or web
+service requests that are expecting something other than proper HTML.
-NOTE: By default, if you use the `:text` option, the text is rendered without using the current layout. If you want Rails to put the text into the current layout, you need to add the `layout: true` option.
+NOTE: By default, if you use the `:plain` option, the text is rendered without
+using the current layout. If you want Rails to put the text into the current
+layout, you need to add the `layout: true` option.
+
+#### Rendering HTML
+
+You can send a HTML string back to the browser by using the `:html` option to
+`render`:
+
+```ruby
+render html: "<strong>Not Found</strong>".html_safe
+```
+
+TIP: This is useful when you're rendering a small snippet of HTML code.
+However, you might want to consider moving it to a template file if the markup
+is complex.
+
+NOTE: This option will escape HTML entities if the string is not html safe.
#### Rendering JSON
@@ -276,6 +295,19 @@ render js: "alert('Hello Rails');"
This will send the supplied string to the browser with a MIME type of `text/javascript`.
+#### Rendering raw body
+
+You can send a raw content back to the browser, without setting any content
+type, by using the `:body` option to `render`:
+
+```ruby
+render body: "raw"
+```
+
+TIP: This option should be used only if you explicitly want the content type to
+be unset. Using `:plain` or `:html` might be more appropriate in most of the
+time.
+
#### Options for `render`
Calls to the `render` method generally accept four options:
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 76722c9ea9..a8b7c9d492 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -329,6 +329,25 @@ User.inactive
# SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'
```
+### Rendering content from string
+
+Rails 4.1 introduces `:plain`, `:html`, and `:body` options to `render`. Those
+options are now the preferred way to render string-based content, as it allows
+you to specify which content type you want the response sent as.
+
+* `render :plain` will set the content type to `text/plain`
+* `render :html` will set the content type to `text/html`
+* `render :body` will *not* set the content type header.
+
+From the security standpoint, if you don't expect to have any markup in your
+response body, you should be using `render :plain` as most browsers will escape
+unsafe content in the response for you.
+
+We will be deprecating the use of `render :text` in a future version. So please
+start using the more precise `:plain:`, `:html`, and `:body` options instead.
+Using `render :text` may pose a security risk, as the content is sent as
+`text/html`.
+
Upgrading from Rails 3.2 to Rails 4.0
-------------------------------------