# frozen_string_literal: true
module ActionController
module Rendering
extend ActiveSupport::Concern
RENDER_FORMATS_IN_PRIORITY = [:body, :plain, :html]
module ClassMethods
# Documentation at ActionController::Renderer#render
delegate :render, to: :renderer
# Returns a renderer instance (inherited from ActionController::Renderer)
# for the controller.
attr_reader :renderer
def setup_renderer! # :nodoc:
@renderer = Renderer.for(self)
end
def inherited(klass)
klass.setup_renderer!
super
end
end
# Renders a template and assigns the result to +self.response_body+.
#
# If no rendering mode option is specified, the template will be derived
# from the first argument.
#
# render "posts/show"
# # => renders app/views/posts/show.html.erb
#
# # In a PostsController action...
# render :show
# # => renders app/views/posts/show.html.erb
#
# If the first argument responds to +render_in+, the template will be
# rendered by calling +render_in+ with the current view context.
#
# ==== \Rendering Mode
#
# [+:partial+]
# See ActionView::PartialRenderer for details.
#
# render partial: "posts/form", locals: { post: Post.new }
# # => renders app/views/posts/_form.html.erb
#
# [+:file+]
# Renders the contents of a file. This option should not be used
# with unsanitized user input.
#
# render file: "/path/to/some/file"
# # => renders /path/to/some/file
#
# [+:inline+]
# Renders an ERB template string.
#
# @name = "World"
# render inline: "
Hello, <%= @name %>!
"
# # => renders "Hello, World!
"
#
# [+:body+]
# Renders the provided text, and sets the content type as +text/plain+.
#
# render body: "Hello, World!"
# # => renders "Hello, World!"
#
# [+:plain+]
# Renders the provided text, and sets the content type as +text/plain+.
#
# render plain: "Hello, World!"
# # => renders "Hello, World!"
#
# [+:html+]
# Renders the provided HTML string, and sets the content type as +text/html+.
# If the string is not +html_safe?+, performs HTML escaping on the string
# before rendering.
#
# render html: "Hello, World!
".html_safe
# # => renders "Hello, World!
"
#
# render html: "Hello, World!
"
# # => renders "<h1>Hello, World!</h1>"
#
# [+:json+]
# Renders the provided object as JSON, and sets the content type as
# +application/json+. If the object is not a string, it will be converted
# to JSON by calling +to_json+.
#
# render json: { hello: "world" }
# # => renders "{\"hello\":\"world\"}"
#
# By default, when a rendering mode is specified, no layout template is
# rendered.
#
# ==== Options
#
# [+:assigns+]
# Hash of instance variable assignments for the template.
#
# render inline: "Hello, <%= @name %>!
", assigns: { name: "World" }
# # => renders "Hello, World!
"
#
# [+:locals+]
# Hash of local variable assignments for the template.
#
# render inline: "Hello, <%= name %>!
", locals: { name: "World" }
# # => renders "Hello, World!
"
#
# [+:layout+]
# The layout template to render. Can also be +false+ or +true+ to disable
# or (re)enable the default layout template.
#
# render "posts/show", layout: "holiday"
# # => renders app/views/posts/show.html.erb with the app/views/layouts/holiday.html.erb layout
#
# render "posts/show", layout: false
# # => renders app/views/posts/show.html.erb with no layout
#
# render inline: "Hello, World!
", layout: true
# # => renders "Hello, World!
" with the default layout
#
# [+:status+]
# The HTTP status code to send with the response. Can be specified as a
# number or as the status name in Symbol form. Defaults to 200.
#
# render "posts/new", status: 422
# # => renders app/views/posts/new.html.erb with HTTP status code 422
#
# render "posts/new", status: :unprocessable_entity
# # => renders app/views/posts/new.html.erb with HTTP status code 422
#
#--
# Check for double render errors and set the content_type after rendering.
def render(*args)
raise ::AbstractController::DoubleRenderError if response_body
super
end
# Similar to #render, but only returns the rendered template as a string,
# instead of setting +self.response_body+.
#--
# Override render_to_string because body can now be set to a Rack body.
def render_to_string(*)
result = super
if result.respond_to?(:each)
string = +""
result.each { |r| string << r }
string
else
result
end
end
def render_to_body(options = {}) # :nodoc:
super || _render_in_priorities(options) || " "
end
private
# Before processing, set the request formats in current controller formats.
def process_action(*) # :nodoc:
self.formats = request.formats.filter_map(&:ref)
super
end
def _process_variant(options)
if defined?(request) && !request.nil? && request.variant.present?
options[:variant] = request.variant
end
end
def _render_in_priorities(options)
RENDER_FORMATS_IN_PRIORITY.each do |format|
return options[format] if options.key?(format)
end
nil
end
def _set_html_content_type
self.content_type = Mime[:html].to_s
end
def _set_rendered_content_type(format)
if format && !response.media_type
self.content_type = format.to_s
end
end
def _set_vary_header
if response.headers["Vary"].blank? && request.should_apply_vary_header?
response.headers["Vary"] = "Accept"
end
end
# Normalize both text and status options.
def _normalize_options(options)
_normalize_text(options)
if options[:html]
options[:html] = ERB::Util.html_escape(options[:html])
end
if options[:status]
options[:status] = Rack::Utils.status_code(options[:status])
end
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
# Process controller specific options, as status, content-type and location.
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
headers["Location"] = url_for(location) if location
super
end
end
end