1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Merge pull request #18546 from brainopia/action_view_render

A shortcut to setup controller environment
This commit is contained in:
Rafael Mendonça França 2015-01-22 12:09:32 -02:00
commit e36ecf4d77
11 changed files with 273 additions and 6 deletions

View file

@ -1,3 +1,15 @@
* Add `ActionController::Renderer` to render arbitrary templates
outside controller actions.
Its functionality is accessible through class methods `render` and
`renderer` of `ActionController::Base`.
*Ravil Bayramgalin*
* Support `:assigns` option when rendering with controllers/mailers.
*Ravil Bayramgalin*
* Default headers, removed in controller actions, are no longer reapplied on
the test response.

View file

@ -11,6 +11,7 @@ module ActionController
autoload :Caching
autoload :Metal
autoload :Middleware
autoload :Renderer
autoload_under "metal" do
autoload :Compatibility

View file

@ -190,11 +190,15 @@ module ActionController
end
def dispatch(name, request) #:nodoc:
set_request!(request)
process(name)
to_a
end
def set_request!(request) #:nodoc:
@_request = request
@_env = request.env
@_env['action_controller.instance'] = self
process(name)
to_a
end
def to_a #:nodoc:

View file

@ -8,9 +8,15 @@ module ActionController
delegate :headers, :status=, :location=, :content_type=,
:status, :location, :content_type, :response_code, :to => "@_response"
def dispatch(action, request)
module ClassMethods
def build_with_env(env = {}) #:nodoc:
new.tap { |c| c.set_request! ActionDispatch::Request.new(env) }
end
end
def set_request!(request) #:nodoc:
super
set_response!(request)
super(action, request)
end
def response_body=(body)

View file

@ -4,6 +4,17 @@ module ActionController
RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html]
module ClassMethods
# Documentation at ActionController::Renderer#render
delegate :render, to: :renderer
# 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

View 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

View file

@ -31,6 +31,15 @@ module BareMetalTest
controller.index
assert_equal ["Hello world"], controller.response_body
end
test "connect a request to controller instance without dispatch" do
env = {}
controller = BareController.new
controller.set_request! ActionDispatch::Request.new(env)
assert controller.request
assert controller.response
assert env['action_controller.instance']
end
end
class HeadController < ActionController::Metal

View file

@ -0,0 +1,103 @@
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 a controller class' do
assert_equal 'Hello world!', ApplicationController.render('test/hello_world')
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

View file

@ -92,12 +92,15 @@ module ActionView
# Find and render a template based on the options given.
# :api: private
def _render_template(options) #:nodoc:
variant = options[:variant]
variant = options.delete(:variant)
assigns = options.delete(:assigns)
context = view_context
context.assign assigns if assigns
lookup_context.rendered_format = nil if options[:formats]
lookup_context.variants = variant if variant
view_renderer.render(view_context, options)
view_renderer.render(context, options)
end
# Assign the rendered format to lookup context.

View file

@ -453,6 +453,10 @@ class TestController < ApplicationController
render :text => "foo"
end
def render_with_assigns_option
render inline: '<%= @hello %>', assigns: { hello: "world" }
end
def yield_content_for
render :action => "content_for", :layout => "yield"
end
@ -1102,6 +1106,11 @@ class RenderTest < ActionController::TestCase
assert_equal "world", assigns["hello"]
end
def test_render_text_with_assigns_option
get :render_with_assigns_option
assert_equal 'world', response.body
end
# :ported:
def test_template_with_locals
get :render_with_explicit_template_with_locals

View file

@ -0,0 +1,6 @@
## Change renderer defaults here.
#
# ApplicationController.renderer.defaults.merge!(
# http_host: 'example.org',
# https: false
# )