mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Add ActionController::Renderer
Render arbitrary templates outside of controller actions
This commit is contained in:
parent
47b6fe0682
commit
801e399e42
5 changed files with 216 additions and 0 deletions
|
@ -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*
|
||||
|
|
|
@ -11,6 +11,7 @@ module ActionController
|
|||
autoload :Caching
|
||||
autoload :Metal
|
||||
autoload :Middleware
|
||||
autoload :Renderer
|
||||
|
||||
autoload_under "metal" do
|
||||
autoload :Compatibility
|
||||
|
|
|
@ -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
|
||||
|
|
103
actionpack/lib/action_controller/renderer.rb
Normal file
103
actionpack/lib/action_controller/renderer.rb
Normal file
|
@ -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
|
99
actionpack/test/controller/renderer_test.rb
Normal file
99
actionpack/test/controller/renderer_test.rb
Normal file
|
@ -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
|
Loading…
Reference in a new issue