aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib')
-rw-r--r--actionpack/lib/action_controller.rb1
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb8
-rw-r--r--actionpack/lib/action_controller/renderer.rb103
3 files changed, 112 insertions, 0 deletions
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index e977bbce99..7667e469d3 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -11,6 +11,7 @@ module ActionController
autoload :Caching
autoload :Metal
autoload :Middleware
+ autoload :Renderer
autoload_under "metal" do
autoload :Compatibility
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index 7bbff0450a..2370c607ed 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -4,6 +4,14 @@ module ActionController
RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html]
+ module ClassMethods
+ # Returns a renderer class (inherited from ActionController::Renderer)
+ # for the controller.
+ def renderer
+ @renderer ||= Renderer.for(self)
+ end
+ end
+
# Before processing, set the request formats in current controller formats.
def process_action(*) #:nodoc:
self.formats = request.formats.map(&:ref).compact
diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb
new file mode 100644
index 0000000000..a122954968
--- /dev/null
+++ b/actionpack/lib/action_controller/renderer.rb
@@ -0,0 +1,103 @@
+module ActionController
+ # ActionController::Renderer allows 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 a shortcut on controller to replace previous example with:
+ #
+ # ApplicationController.render template: '...'
+ #
+ # #render method allows you to use any options as when rendering in 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
+ class_attribute :controller, :defaults
+ # Rack environment to render templates in.
+ attr_reader :env
+
+ class << self
+ delegate :render, to: :new
+
+ # Create a new renderer class for a specific controller class.
+ def for(controller)
+ Class.new self do
+ self.controller = controller
+ self.defaults = {
+ http_host: 'example.org',
+ https: false,
+ method: 'get',
+ script_name: '',
+ 'rack.input' => ''
+ }
+ end
+ end
+ end
+
+ # Accepts a custom Rack environment to render templates in.
+ # It will be merged with ActionController::Renderer.defaults
+ def initialize(env = {})
+ @env = normalize_keys(defaults).merge normalize_keys(env)
+ @env['action_dispatch.routes'] = controller._routes
+ end
+
+ # Render templates with any options from ActionController::Base#render_to_string.
+ def render(*args)
+ raise 'missing controller' unless controller?
+
+ instance = controller.build_with_env(env)
+ instance.render_to_string(*args)
+ end
+
+ private
+ def normalize_keys(env)
+ env.dup.tap do |new_env|
+ convert_symbols! new_env
+ handle_method_key! new_env
+ handle_https_key! new_env
+ end
+ end
+
+ def convert_symbols!(env)
+ env.keys.each do |key|
+ if key.is_a? Symbol
+ value = env.delete key
+ key = key.to_s.upcase
+ env[key] = value
+ end
+ end
+ end
+
+ def handle_method_key!(env)
+ if method = env.delete('METHOD')
+ env['REQUEST_METHOD'] = method.upcase
+ end
+ end
+
+ def handle_https_key!(env)
+ if env.has_key? 'HTTPS'
+ env['HTTPS'] = env['HTTPS'] ? 'on' : 'off'
+ end
+ end
+ end
+end