1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
|
# frozen_string_literal: true
module ActionController
# ActionController::Renderer allows you to render arbitrary templates
# without requirement of being in controller actions.
#
# You get a concrete renderer class by invoking ActionController::Base#renderer.
# For example:
#
# ApplicationController.renderer
#
# It allows you to call method #render directly.
#
# ApplicationController.renderer.render template: '...'
#
# You can use this shortcut in a controller, instead of the previous example:
#
# ApplicationController.render template: '...'
#
# #render allows you to use the same options that you can use when rendering in a controller.
# For example:
#
# FooController.render :action, locals: { ... }, assigns: { ... }
#
# The template will be rendered in a Rack environment which is accessible through
# ActionController::Renderer#env. You can set it up in two ways:
#
# * by changing renderer defaults, like
#
# ApplicationController.renderer.defaults # => hash with default Rack environment
#
# * by initializing an instance of renderer by passing it a custom environment.
#
# ApplicationController.renderer.new(method: 'post', https: true)
#
class Renderer
attr_reader :defaults, :controller
DEFAULTS = {
http_host: "example.org",
https: false,
method: "get",
script_name: "",
input: ""
}.freeze
# Create a new renderer instance for a specific controller class.
def self.for(controller, env = {}, defaults = DEFAULTS.dup)
new(controller, env, defaults)
end
# Create a new renderer for the same controller but with a new env.
def new(env = {})
self.class.new controller, env, defaults
end
# Create a new renderer for the same controller but with new defaults.
def with_defaults(defaults)
self.class.new controller, @env, self.defaults.merge(defaults)
end
# Accepts a custom Rack environment to render templates in.
# It will be merged with the default Rack environment defined by
# +ActionController::Renderer::DEFAULTS+.
def initialize(controller, env, defaults)
@controller = controller
@defaults = defaults
@env = normalize_keys defaults, env
end
# Render templates with any options from ActionController::Base#render_to_string.
#
# The primary options are:
# * <tt>:partial</tt> - See <tt>ActionView::PartialRenderer</tt> for details.
# * <tt>:file</tt> - Renders an explicit template file. Add <tt>:locals</tt> to pass in, if so desired.
# It shouldn’t be used directly with unsanitized user input due to lack of validation.
# * <tt>:inline</tt> - Renders an ERB template string.
# * <tt>:plain</tt> - Renders provided text and sets the content type as <tt>text/plain</tt>.
# * <tt>:html</tt> - Renders the provided HTML safe string, otherwise
# performs HTML escape on the string first. Sets the content type as <tt>text/html</tt>.
# * <tt>:json</tt> - Renders the provided hash or object in JSON. You don't
# need to call <tt>.to_json</tt> on the object you want to render.
# * <tt>:body</tt> - Renders provided text and sets content type of <tt>text/plain</tt>.
#
# If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, then:
#
# If an object responding to +render_in+ is passed, +render_in+ is called on the object,
# passing in the current view context.
#
# Otherwise, a partial is rendered using the second parameter as the locals hash.
def render(*args)
raise "missing controller" unless controller
request = ActionDispatch::Request.new @env
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
def normalize_keys(defaults, env)
new_env = {}
env.each_pair { |k, v| new_env[rack_key_for(k)] = rack_value_for(k, v) }
defaults.each_pair do |k, v|
key = rack_key_for(k)
new_env[key] = rack_value_for(k, v) unless new_env.key?(key)
end
new_env["rack.url_scheme"] = new_env["HTTPS"] == "on" ? "https" : "http"
new_env
end
RACK_KEY_TRANSLATION = {
http_host: "HTTP_HOST",
https: "HTTPS",
method: "REQUEST_METHOD",
script_name: "SCRIPT_NAME",
input: "rack.input"
}
def rack_key_for(key)
RACK_KEY_TRANSLATION[key] || key.to_s
end
def rack_value_for(key, value)
case key
when :https
value ? "on" : "off"
when :method
-value.upcase
else
value
end
end
end
end
|