aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
authorbrainopia <brainopia@evilmartians.com>2015-01-18 03:06:10 +0300
committerbrainopia <brainopia@evilmartians.com>2015-01-22 01:02:11 +0300
commit801e399e42cab610860e307f2dd77c1edb6e1fac (patch)
tree55d75fb371bfe9f0651177364e5eed0465bf9858 /actionpack
parent47b6fe06824893f9b0c4628e766f387956cabf22 (diff)
downloadrails-801e399e42cab610860e307f2dd77c1edb6e1fac.tar.gz
rails-801e399e42cab610860e307f2dd77c1edb6e1fac.tar.bz2
rails-801e399e42cab610860e307f2dd77c1edb6e1fac.zip
Add ActionController::Renderer
Render arbitrary templates outside of controller actions
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/CHANGELOG.md5
-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
-rw-r--r--actionpack/test/controller/renderer_test.rb99
5 files changed, 216 insertions, 0 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index a9e8b82107..d8c26fa281 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,8 @@
+* Add `ActionController::Renderer` to render arbitrary templates
+ outside controller actions.
+
+ *Ravil Bayramgalin*
+
* Support `:assigns` option when rendering with controllers/mailers.
*Ravil Bayramgalin*
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
diff --git a/actionpack/test/controller/renderer_test.rb b/actionpack/test/controller/renderer_test.rb
new file mode 100644
index 0000000000..6efc9c8dfd
--- /dev/null
+++ b/actionpack/test/controller/renderer_test.rb
@@ -0,0 +1,99 @@
+require 'abstract_unit'
+
+class RendererTest < ActiveSupport::TestCase
+ test 'creating with a controller' do
+ controller = CommentsController
+ renderer = ActionController::Renderer.for controller
+
+ assert_equal controller, renderer.controller
+ end
+
+ test 'creating from a controller' do
+ controller = AccountsController
+ renderer = controller.renderer
+
+ assert_equal controller, renderer.controller
+ end
+
+ test 'rendering with a class renderer' do
+ renderer = ApplicationController.renderer
+ content = renderer.render template: 'ruby_template'
+
+ assert_equal 'Hello from Ruby code', content
+ end
+
+ test 'rendering with an instance renderer' do
+ renderer = ApplicationController.renderer.new
+ content = renderer.render file: 'test/hello_world'
+
+ assert_equal 'Hello world!', content
+ end
+
+ test 'rendering with locals' do
+ renderer = ApplicationController.renderer
+ content = renderer.render template: 'test/render_file_with_locals',
+ locals: { secret: 'bar' }
+
+ assert_equal "The secret is bar\n", content
+ end
+
+ test 'rendering with assigns' do
+ renderer = ApplicationController.renderer
+ content = renderer.render template: 'test/render_file_with_ivar',
+ assigns: { secret: 'foo' }
+
+ assert_equal "The secret is foo\n", content
+ end
+
+ test 'rendering with custom env' do
+ renderer = ApplicationController.renderer.new method: 'post'
+ content = renderer.render inline: '<%= request.post? %>'
+
+ assert_equal 'true', content
+ end
+
+ test 'rendering with defaults' do
+ renderer = ApplicationController.renderer
+ renderer.defaults[:https] = true
+ content = renderer.render inline: '<%= request.ssl? %>'
+
+ assert_equal 'true', content
+ end
+
+ test 'same defaults from the same controller' do
+ defaults = ->(controller) { controller.renderer.defaults }
+
+ assert defaults[AccountsController].equal? defaults[AccountsController]
+ assert_not defaults[AccountsController].equal? defaults[CommentsController]
+ end
+
+ test 'rendering with different formats' do
+ html = 'Hello world!'
+ xml = "<p>Hello world!</p>\n"
+
+ assert_equal html, render['respond_to/using_defaults']
+ assert_equal xml, render['respond_to/using_defaults.xml.builder']
+ assert_equal xml, render['respond_to/using_defaults', formats: :xml]
+ end
+
+ test 'rendering with helpers' do
+ assert_equal "<p>1\n<br />2</p>", render[inline: '<%= simple_format "1\n2" %>']
+ end
+
+ test 'rendering from inherited renderer' do
+ inherited = Class.new ApplicationController.renderer do
+ defaults[:script_name] = 'script'
+ def render(options)
+ super options.merge(locals: { param: :value })
+ end
+ end
+
+ template = '<%= url_for controller: :foo, action: :bar, param: param %>'
+ assert_equal 'script/foo/bar?param=value', inherited.render(inline: template)
+ end
+
+ private
+ def render
+ @render ||= ApplicationController.renderer.method(:render)
+ end
+end