Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Introduce `:plain`, `:html`, and `:body` render options. #14062

Merged
merged 10 commits into from

7 participants

Prem Sichanugrist David Heinemeier Hansson Carlos Antonio da Silva Rafael Mendonça França Robin Dupret Jeremy Kemper Aaron Patterson
Prem Sichanugrist
Collaborator

This is a continuation from #12374, with slight modification since the discussion went a bit off-topic.


Per discussion, render :text misdirect people to think that it would render content with text/plain MIME type. However, render :text actually sets the response body directly, and inherits the default response MIME type, which is text/html.

In order to reduce confusion, we're introducing 3 more render format to render:

render html: '<strong>HTML String</strong>'.html_safe # render with `text/html` MIME type.

render plain: 'plain text' # render with `text/plain` MIME type.

render body: 'raw body' # render raw content, does not set content type.

We want to phrase out the usage of render :text, to reduce the confusion in the future. There were some discussion about deprecate render :text, but we haven't come into a conclusion yet. So, we'll consider doing that before the next major release.

Fixes #12374

Prem Sichanugrist sikachu added this to the 4.1.0 milestone
Prem Sichanugrist sikachu self-assigned this
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].freeze
Rafael Mendonça França Owner

We don't need to freeze this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
actionpack/lib/action_controller/metal/rendering.rb
((17 lines not shown))
super
self.content_type ||= format.to_s
+
+ if options[:body].present?
+ self.content_type = "none"
Rafael Mendonça França Owner

Why we need to set to none?

Prem Sichanugrist Collaborator
sikachu added a note

none is mode like a placeholder, so that we can detect and remove the content type later. (nil and false doesn't work as we're using ||= somewhere else, and then text/html got set back again).

Rafael Mendonça França Owner

I see, it is to short circuit the assign_default_content_type_and_charset Maybe we should refactor to not call it when the render is body instead of using short-circuit values. WDYT?

Prem Sichanugrist Collaborator
sikachu added a note

I've checked earlier and at the time that assign_default_content_type_and_charset! got called, we already lost context on what option render was called with, as they got rendered to string and put back in @body.

What do you think would be an acceptable way of doing this?

Prem Sichanugrist Collaborator
sikachu added a note

Actually, found out a way to do this without needing this value. PR updating!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
actionpack/lib/action_controller/metal/rendering.rb
((17 lines not shown))
super
self.content_type ||= format.to_s
+
+ if options[:body].present?
Rafael Mendonça França Owner

Is not if options[:body] sufficient?

Prem Sichanugrist Collaborator
sikachu added a note

Ha, true, going to change that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
actionpack/lib/action_controller/metal/rendering.rb
@@ -46,12 +61,14 @@ def _normalize_args(action=nil, options={}, &blk) #:nodoc:
# 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].present?
Rafael Mendonça França Owner

Should not be only if options[:html]?

Prem Sichanugrist Collaborator
sikachu added a note

Yep, changing this.

Prem Sichanugrist Collaborator
sikachu added a note

Fixed. Can you give it another review?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
actionpack/CHANGELOG.md
@@ -1,3 +1,32 @@
+* Introduce `render :html` as an option for render HTML content with a

option to render?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
actionpack/CHANGELOG.md
@@ -1,3 +1,32 @@
+* Introduce `render :html` as an option for 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 for render content with a content
+ type of `text/plain`. This is a preferred option if you are planning to

the preferred?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
actionpack/lib/action_controller/metal/rendering.rb
((6 lines not shown))
end
private
- def _process_format(format)
+ def _render_in_priorities(options)
+ available_format = RENDER_FORMATS_IN_PRIORITY.detect { |format| options[format].present? }
+ options[available_format]
+ end

This should be faster without the present? check, and possibly with key? only. But probably each + return should yield even better results:

  RENDER_FORMATS_IN_PRIORITY.each { |format|
    return options[format] if options.key?(format)
  }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
actionpack/lib/action_controller/metal/rendering.rb
((17 lines not shown))
super
self.content_type ||= format.to_s
+
+ if options[:body]
+ self.headers.delete "Content-Type"
+ end
+
+ if options[:plain]
+ self.content_type = Mime::TEXT
+ end

How about:

if options[:body]
  self.headers.delete "Content-Type"
elsif options[:plain]
  self.content_type = Mime::TEXT
else
  self.content_type ||= format.to_s
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Carlos Antonio da Silva carlosantoniodasilva commented on the diff
actionview/lib/action_view/rendering.rb
((6 lines not shown))
super
+
+ if options[:body]
+ self.no_content_type = true
+ end

Why would the view mess up with the response content type? =(

Prem Sichanugrist Collaborator
sikachu added a note

I have a weird feeling about this too, but I don't see another places where we can tell the response that it should skip setting the content type, as in other places we already lose context on which option has been passed to render.

Prem Sichanugrist Collaborator
sikachu added a note

Any suggestion on where I should put this?

It just looks like render :body shouldn't be something Action View is aware of, it seems a controller responsibility only?

Prem Sichanugrist Collaborator
sikachu added a note

@carlosantoniodasilva my thought exactly! I was surprised as well that render :text is actually handled by AV and not AC when I started this patch.

However, unless you are using a minimal controller (Just inherits from AC::Metal and include rendering), AV is the one handling render :text. I think we can fix that in the future release, if we think that's not right.

Yeah, awkward indeed. Anyway nothing comes to my mind right now so I guess that's ok.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
actionview/test/template/render_test.rb
@@ -22,7 +22,7 @@ def setup_view(paths)
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

Please wrap with (), otherwise it's gonna warn.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Robin Dupret robin850 removed the attached PR label
guides/source/layouts_and_rendering.md
((10 lines not shown))
```
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.
Robin Dupret Collaborator

Could you wrap new additions in the guides around 80 chars please ?

Awesome work so far! :heart:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
David Heinemeier Hansson
Owner

@sikachu, do you expect to be able to wrap this up today or should we delay it to 4.2.0?

Prem Sichanugrist
Collaborator
Prem Sichanugrist
Collaborator

I've addressed all of the comments so far. This should be ready to go.

guides/source/layouts_and_rendering.md
@@ -276,6 +298,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"
Rafael Mendonça França Owner

this "ruby" is not in the wrong line?

Prem Sichanugrist Collaborator
sikachu added a note
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sikachu added some commits
Prem Sichanugrist sikachu Introduce `render :body` for render raw content
This is 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.
103e18c
Prem Sichanugrist sikachu Update hash format for render_text_test 9e9cc66
Prem Sichanugrist sikachu Introduce `render :plain` for render plain text
This is 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.
8cd9f6d
Prem Sichanugrist sikachu Introduce `render :html` for render HTML string
This is an option for to 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.
920f3ba
Prem Sichanugrist sikachu Fix a fragile test on `action_view/render`
This test were assuming that the list of render options will always be
the same. Fixing that so this doesn't break when we add/remove render
option in the future.
243e6e4
Prem Sichanugrist sikachu Cleanup `ActionController::Rendering` 79c4983
Prem Sichanugrist sikachu Update guides for new rendering options
* Introduces `:plain`, `:html`, `:body` render option.
* Update guide to use `render :plain` instead of `render :text`.
76be30f
Prem Sichanugrist sikachu Add missing CHANGELOG entry to Action View 9fe506e
Prem Sichanugrist sikachu Add `#no_content_type` attribute to `AD::Response`
Setting this attribute to `true` will remove the content type header
from the request. This is use in `render :body` feature.
3047376
Prem Sichanugrist sikachu Update upgrading guide regarding `render :text` ede0f8c
Rafael Mendonça França rafaelfranca merged commit acc0e63 into from
Prem Sichanugrist sikachu deleted the branch
Aaron Patterson tenderlove referenced this pull request from a commit
Aaron Patterson tenderlove Merge branch 'master' into jobs
* master: (1455 commits)
  change 'assert !' to 'assert_not' in guides [ci skip]
  Pointing to latest guides [ci skip]
  Methods silence_stream/quietly are not thread-safe [skip ci]
  [ci skip] Close the meta tag with '/>' instead of '>'
  Fix render plain docs example in AM::Base
  Update Docs in favor to use render plain instead of text option ref #14062
  Typo fix for unscope
  Use the reference for the mime type to get the format
  Preparing for 4.1.0.beta2 release
  Correctly escape PostgreSQL arrays.
  Escape format, negative_format and units options of number helpers
  Sync 4.1 release notes with changes since 7f648bc [ci skip]
  Update upgrading guide regarding `render :text`
  Add `#no_content_type` attribute to `AD::Response`
  Add missing CHANGELOG entry to Action View
  Update guides for new rendering options
  Cleanup `ActionController::Rendering`
  Fix a fragile test on `action_view/render`
  Introduce `render :html` for render HTML string
  Introduce `render :plain` for render plain text
  ...

Conflicts:
	actionmailer/lib/action_mailer/railtie.rb
	railties/lib/rails/application.rb
	railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
	railties/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt
96a3216
Aaron Patterson

Why are we mutating the header hash here? It didn't used to do that.

If you're using a streaming response, this will break because the header hash is frozen (since it has already been sent to the client).

Also, what if someone specifically set a content type in their response? Do we actually want to delete it? For example:

class MainController < ApplicationController
  def index
    response.headers['Content-Type'] = 'application/json'
    render body: JSON.dump([1,2,3,4])
  end
end

I know we have a render json thing, but this is just an example. Are we sure unconditionally rm'ing the content type header is The Right Thing™?

/cc @jeremy

Owner

:+1: we want to not set the default content type, but we don't want to erase the content type you provided—that's precisely why people would be using render body: ... :grin:

Collaborator

Yep, this patch is going too far. Going to revert this then :sweat_smile:.

See #14238 for more detail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 18, 2014
  1. Prem Sichanugrist

    Introduce `render :body` for render raw content

    sikachu authored
    This is 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.
  2. Prem Sichanugrist
  3. Prem Sichanugrist

    Introduce `render :plain` for render plain text

    sikachu authored
    This is 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.
  4. Prem Sichanugrist

    Introduce `render :html` for render HTML string

    sikachu authored
    This is an option for to 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.
  5. Prem Sichanugrist

    Fix a fragile test on `action_view/render`

    sikachu authored
    This test were assuming that the list of render options will always be
    the same. Fixing that so this doesn't break when we add/remove render
    option in the future.
  6. Prem Sichanugrist
  7. Prem Sichanugrist

    Update guides for new rendering options

    sikachu authored
    * Introduces `:plain`, `:html`, `:body` render option.
    * Update guide to use `render :plain` instead of `render :text`.
  8. Prem Sichanugrist
  9. Prem Sichanugrist

    Add `#no_content_type` attribute to `AD::Response`

    sikachu authored
    Setting this attribute to `true` will remove the content type header
    from the request. This is use in `render :body` feature.
  10. Prem Sichanugrist
This page is out of date. Refresh to see the latest.
Showing with 762 additions and 41 deletions.
  1. +29 −0 actionpack/CHANGELOG.md
  2. +2 −2 actionpack/lib/abstract_controller/rendering.rb
  3. +2 −2 actionpack/lib/action_controller/metal/rack_delegation.rb
  4. +38 −7 actionpack/lib/action_controller/metal/rendering.rb
  5. +12 −1 actionpack/lib/action_dispatch/http/response.rb
  6. +175 −0 actionpack/test/controller/new_base/render_body_test.rb
  7. +190 −0 actionpack/test/controller/new_base/render_html_test.rb
  8. +168 −0 actionpack/test/controller/new_base/render_plain_test.rb
  9. +18 −18 actionpack/test/controller/new_base/render_text_test.rb
  10. +8 −0 actionpack/test/dispatch/response_test.rb
  11. +5 −0 actionview/CHANGELOG.md
  12. +7 −0 actionview/lib/action_view/helpers/rendering_helper.rb
  13. +1 −1  actionview/lib/action_view/layouts.rb
  14. +8 −2 actionview/lib/action_view/renderer/template_renderer.rb
  15. +6 −1 actionview/lib/action_view/rendering.rb
  16. +1 −0  actionview/lib/action_view/template.rb
  17. +34 −0 actionview/lib/action_view/template/html.rb
  18. +1 −1  actionview/test/template/render_test.rb
  19. +1 −1  guides/source/action_controller_overview.md
  20. +1 −1  guides/source/getting_started.md
  21. +36 −4 guides/source/layouts_and_rendering.md
  22. +19 −0 guides/source/upgrading_ruby_on_rails.md
29 actionpack/CHANGELOG.md
View
@@ -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.
4 actionpack/lib/abstract_controller/rendering.rb
View
@@ -23,7 +23,7 @@ module Rendering
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 @@ def _process_options(options)
# Process the rendered format.
# :api: private
- def _process_format(format)
+ def _process_format(format, options = {})
end
# Normalize args and options.
4 actionpack/lib/action_controller/metal/rack_delegation.rb
View
@@ -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)
45 actionpack/lib/action_controller/metal/rendering.rb
View
@@ -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 @@ def render_to_string(*)
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 @@ def _normalize_args(action=nil, options={}, &blk) #:nodoc:
# 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 @@ def _normalize_options(options) #:nodoc:
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)
13 actionpack/lib/action_dispatch/http/response.rb
View
@@ -63,6 +63,8 @@ class Response
# 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 @@ def append_charset?
!@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)
175 actionpack/test/controller/new_base/render_body_test.rb
View
@@ -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
190 actionpack/test/controller/new_base/render_html_test.rb
View
@@ -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
168 actionpack/test/controller/new_base/render_plain_test.rb
View
@@ -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
36 actionpack/test/controller/new_base/render_text_test.rb
View
@@ -14,7 +14,7 @@ class SimpleController < ActionController::Base
self.view_paths = [ActionView::FixtureResolver.new]
def index
- render :text => "hello david"
+ render text: "hello david"
end
end
@@ -26,48 +26,48 @@ class WithLayoutController < ::ApplicationController
)]
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 @@ class RenderTextTest < Rack::TestCase
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 @@ class RenderTextTest < Rack::TestCase
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 @@ class RenderTextTest < Rack::TestCase
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"
8 actionpack/test/dispatch/response_test.rb
View
@@ -235,6 +235,14 @@ def test_response_body_encoding
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
5 actionview/CHANGELOG.md
View
@@ -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`.
7 actionview/lib/action_view/helpers/rendering_helper.rb
View
@@ -12,6 +12,13 @@ module RenderingHelper
# * <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.
2  actionview/lib/action_view/layouts.rb
View
@@ -420,7 +420,7 @@ def _default_layout(require_layout = false)
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
10 actionview/lib/action_view/renderer/template_renderer.rb
View
@@ -21,8 +21,14 @@ def render(context, options)
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 @@ def determine_template(options) #:nodoc:
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
7 actionview/lib/action_view/rendering.rb
View
@@ -100,8 +100,13 @@ def _render_template(options) #:nodoc:
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

Why would the view mess up with the response content type? =(

Prem Sichanugrist Collaborator
sikachu added a note

I have a weird feeling about this too, but I don't see another places where we can tell the response that it should skip setting the content type, as in other places we already lose context on which option has been passed to render.

Prem Sichanugrist Collaborator
sikachu added a note

Any suggestion on where I should put this?

It just looks like render :body shouldn't be something Action View is aware of, it seems a controller responsibility only?

Prem Sichanugrist Collaborator
sikachu added a note

@carlosantoniodasilva my thought exactly! I was surprised as well that render :text is actually handled by AV and not AC when I started this patch.

However, unless you are using a minimal controller (Just inherits from AC::Metal and include rendering), AV is the one handling render :text. I think we can fix that in the future release, if we think that's not right.

Yeah, awkward indeed. Anyway nothing comes to my mind right now so I guess that's ok.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
lookup_context.formats = [format.to_sym]
lookup_context.rendered_format = lookup_context.formats.first
end
1  actionview/lib/action_view/template.rb
View
@@ -90,6 +90,7 @@ class Template
eager_autoload do
autoload :Error
autoload :Handlers
+ autoload :HTML
autoload :Text
autoload :Types
end
34 actionview/lib/action_view/template/html.rb
View
@@ -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
2  actionview/test/template/render_test.rb
View
@@ -22,7 +22,7 @@ def setup_view(paths)
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
2  guides/source/action_controller_overview.md
View
@@ -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
```
2  guides/source/getting_started.md
View
@@ -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
```
40 guides/source/layouts_and_rendering.md
View
@@ -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:
19 guides/source/upgrading_ruby_on_rails.md
View
@@ -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
-------------------------------------
Something went wrong with that request. Please try again.