lib/action_controller/renderer.rb
# frozen_string_literal: true 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