rails/actionpack/lib/action_controller/renderer.rb
2024-02-09 22:28:15 +00:00

162 lines
5.2 KiB
Ruby

# frozen_string_literal: true
# :markup: markdown
module ActionController
# # Action Controller Renderer
#
# ActionController::Renderer allows you to render arbitrary templates without
# being inside a controller action.
#
# You can get a renderer instance by calling `renderer` on a controller class:
#
# ApplicationController.renderer
# PostsController.renderer
#
# and render a template by calling the #render method:
#
# ApplicationController.renderer.render template: "posts/show", assigns: { post: Post.first }
# PostsController.renderer.render :show, assigns: { post: Post.first }
#
# As a shortcut, you can also call `render` directly on the controller class
# itself:
#
# ApplicationController.render template: "posts/show", assigns: { post: Post.first }
# PostsController.render :show, assigns: { post: Post.first }
#
class Renderer
attr_reader :controller
DEFAULTS = {
method: "get",
input: ""
}.freeze
def self.normalize_env(env) # :nodoc:
new_env = {}
env.each_pair do |key, value|
case key
when :https
value = value ? "on" : "off"
when :method
value = -value.upcase
end
key = RACK_KEY_TRANSLATION[key] || key.to_s
new_env[key] = value
end
if new_env["HTTP_HOST"]
new_env["HTTPS"] ||= "off"
new_env["SCRIPT_NAME"] ||= ""
end
if new_env["HTTPS"]
new_env["rack.url_scheme"] = new_env["HTTPS"] == "on" ? "https" : "http"
end
new_env
end
# Creates a new renderer using the given controller class. See ::new.
def self.for(controller, env = nil, defaults = DEFAULTS)
new(controller, env, defaults)
end
# Creates a new renderer using the same controller, but with a new Rack env.
#
# ApplicationController.renderer.new(method: "post")
#
def new(env = nil)
self.class.new controller, env, @defaults
end
# Creates a new renderer using the same controller, but with the given defaults
# merged on top of the previous defaults.
def with_defaults(defaults)
self.class.new controller, @env, @defaults.merge(defaults)
end
# Initializes a new Renderer.
#
# #### Parameters
#
# * `controller` - The controller class to instantiate for rendering.
# * `env` - The Rack env to use for mocking a request when rendering. Entries
# can be typical Rack env keys and values, or they can be any of the
# following, which will be converted appropriately:
# * `:http_host` - The HTTP host for the incoming request. Converts to
# Rack's `HTTP_HOST`.
# * `:https` - Boolean indicating whether the incoming request uses HTTPS.
# Converts to Rack's `HTTPS`.
# * `:method` - The HTTP method for the incoming request,
# case-insensitive. Converts to Rack's `REQUEST_METHOD`.
# * `:script_name` - The portion of the incoming request's URL path that
# corresponds to the application. Converts to Rack's `SCRIPT_NAME`.
# * `:input` - The input stream. Converts to Rack's `rack.input`.
#
# * `defaults` - Default values for the Rack env. Entries are specified in the
# same format as `env`. `env` will be merged on top of these values.
# `defaults` will be retained when calling #new on a renderer instance.
#
#
# If no `http_host` is specified, the env HTTP host will be derived from the
# routes' `default_url_options`. In this case, the `https` boolean and the
# `script_name` will also be derived from `default_url_options` if they were not
# specified. Additionally, the `https` boolean will fall back to
# `Rails.application.config.force_ssl` if `default_url_options` does not specify
# a `protocol`.
def initialize(controller, env, defaults)
@controller = controller
@defaults = defaults
if env.blank? && @defaults == DEFAULTS
@env = DEFAULT_ENV
else
@env = normalize_env(@defaults)
@env.merge!(normalize_env(env)) unless env.blank?
end
end
def defaults
@defaults = @defaults.dup if @defaults.frozen?
@defaults
end
# Renders a template to a string, just like
# ActionController::Rendering#render_to_string.
def render(*args)
request = ActionDispatch::Request.new(env_for_request)
request.routes = controller._routes
instance = controller.new
instance.set_request! request
instance.set_response! controller.make_response!(request)
instance.render_to_string(*args)
end
alias_method :render_to_string, :render # :nodoc:
private
RACK_KEY_TRANSLATION = {
http_host: "HTTP_HOST",
https: "HTTPS",
method: "REQUEST_METHOD",
script_name: "SCRIPT_NAME",
input: "rack.input"
}
DEFAULT_ENV = normalize_env(DEFAULTS).freeze # :nodoc:
delegate :normalize_env, to: :class
def env_for_request
if @env.key?("HTTP_HOST") || controller._routes.nil?
@env.dup
else
controller._routes.default_env.merge(@env)
end
end
end
end