Pass source to template handler and deprecate old style handler

This commit passes the mutated source to the template handler as a
parameter and deprecates the old handlers.  Old handlers required that
templates contain a reference to mutated source code, but we would like
to make template objects "read only".  This change lets the template
remain "read only" while still giving template handlers access to the
source code after mutations.
This commit is contained in:
Aaron Patterson 2019-02-01 16:10:02 -08:00
parent 2169bd3d2a
commit 28f88e0074
No known key found for this signature in database
GPG Key ID: 953170BCB4FFAFC6
10 changed files with 55 additions and 21 deletions

View File

@ -2,7 +2,6 @@
require "active_support/core_ext/object/try"
require "active_support/core_ext/kernel/singleton_class"
require "active_support/deprecation"
require "thread"
require "delegate"
@ -304,7 +303,7 @@ module ActionView
# regardless of the original source encoding.
def compile(mod)
source = encode!
code = @handler.call(LegacyTemplate.new(self, source))
code = @handler.call(self, source)
# Make sure that the resulting String to be eval'd is in the
# encoding of the code

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true
require "active_support/deprecation"
module ActionView #:nodoc:
# = Action View Template Handlers
class Template #:nodoc:
@ -14,7 +16,7 @@ module ActionView #:nodoc:
base.register_template_handler :erb, ERB.new
base.register_template_handler :html, Html.new
base.register_template_handler :builder, Builder.new
base.register_template_handler :ruby, :source.to_proc
base.register_template_handler :ruby, lambda { |_, source| source }
end
@@template_handlers = {}
@ -24,11 +26,35 @@ module ActionView #:nodoc:
@@template_extensions ||= @@template_handlers.keys
end
class LegacyHandlerWrapper < SimpleDelegator # :nodoc:
def call(view, source)
__getobj__.call(ActionView::Template::LegacyTemplate.new(view, source))
end
end
# Register an object that knows how to handle template files with the given
# extensions. This can be used to implement new template types.
# The handler must respond to +:call+, which will be passed the template
# and should return the rendered template as a String.
def register_template_handler(*extensions, handler)
params = if handler.is_a?(Proc)
handler.parameters
else
handler.method(:call).parameters
end
unless params.find_all { |type, _| type == :req }.length >= 2
ActiveSupport::Deprecation.warn <<~eowarn
Single arity template handlers are deprecated. Template handlers must
now accept two parameters, the view object and the source for the view object.
Change:
>> #{handler.class}#call(#{params.map(&:last).join(", ")})
To:
>> #{handler.class}#call(#{params.map(&:last).join(", ")}, source)
eowarn
handler = LegacyHandlerWrapper.new(handler)
end
raise(ArgumentError, "Extension is required") if extensions.empty?
extensions.each do |extension|
@@template_handlers[extension.to_sym] = handler

View File

@ -5,11 +5,11 @@ module ActionView
class Builder
class_attribute :default_format, default: :xml
def call(template)
def call(template, source)
require_engine
"xml = ::Builder::XmlMarkup.new(:indent => 2);" \
"self.output_buffer = xml.target!;" +
template.source +
source +
";xml.target!;"
end

View File

@ -28,8 +28,8 @@ module ActionView
ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
def self.call(template)
new.call(template)
def self.call(template, source)
new.call(template, source)
end
def supports_streaming?
@ -40,17 +40,17 @@ module ActionView
true
end
def call(template)
def call(template, source)
# First, convert to BINARY, so in case the encoding is
# wrong, we can still find an encoding tag
# (<%# encoding %>) inside the String using a regular
# expression
template_source = template.source.dup.force_encoding(Encoding::ASCII_8BIT)
template_source = source.dup.force_encoding(Encoding::ASCII_8BIT)
erb = template_source.gsub(ENCODING_TAG, "")
encoding = $2
erb.force_encoding valid_encoding(template.source.dup, encoding)
erb.force_encoding valid_encoding(source.dup, encoding)
# Always make sure we return a String in the default_internal
erb.encode!

View File

@ -3,7 +3,7 @@
module ActionView
module Template::Handlers
class Html < Raw
def call(template)
def call(template, source)
"ActionView::OutputBuffer.new #{super}"
end
end

View File

@ -3,8 +3,8 @@
module ActionView
module Template::Handlers
class Raw
def call(template)
"#{template.source.inspect}.html_safe;"
def call(template, source)
"#{source.inspect}.html_safe;"
end
end
end

View File

@ -58,7 +58,7 @@ module RenderERBUtils
template = ActionView::Template.new(
string.strip,
"test template",
ActionView::Template::Handlers::ERB,
ActionView::Template.handler_for_extension(:erb),
{})
template.render(ActionView::Base.empty, {}).strip

View File

@ -70,7 +70,7 @@ class LayoutAutoDiscoveryTest < ActionController::TestCase
end
def test_third_party_template_library_auto_discovers_layout
with_template_handler :mab, lambda { |template| template.source.inspect } do
with_template_handler :mab, lambda { |template, source| source.inspect } do
@controller = ThirdPartyTemplateLibraryController.new
get :hello
assert_response :success
@ -212,7 +212,7 @@ class LayoutSetInResponseTest < ActionController::TestCase
end
def test_layout_set_when_using_render
with_template_handler :mab, lambda { |template| template.source.inspect } do
with_template_handler :mab, lambda { |template, source| source.inspect } do
@controller = SetsLayoutInRenderController.new
get :hello
assert_includes @response.body, "layouts/third_party_template_library.mab"

View File

@ -17,8 +17,8 @@ class FakeTemplate
end
end
Neckbeard = lambda { |template| template.source }
Bowtie = lambda { |template| template.source }
Neckbeard = lambda { |template, source| source }
Bowtie = lambda { |template, source| source }
class DependencyTrackerTest < ActionView::TestCase
def tracker

View File

@ -450,13 +450,22 @@ module RenderTestCases
assert_equal "Hello, World!", @view.render(inline: "Hello, World!", type: :bar)
end
CustomHandler = lambda do |template|
CustomHandler = lambda do |template, source|
"@output_buffer = ''.dup\n" \
"@output_buffer << 'source: #{template.source.inspect}'\n"
"@output_buffer << 'source: #{source.inspect}'\n"
end
def test_render_inline_with_render_from_to_proc
ActionView::Template.register_template_handler :ruby_handler, :source.to_proc
ActionView::Template.register_template_handler :ruby_handler, lambda { |_, source| source }
assert_equal "3", @view.render(inline: "(1 + 2).to_s", type: :ruby_handler)
ensure
ActionView::Template.unregister_template_handler :ruby_handler
end
def test_render_inline_with_render_from_to_proc_deprecated
assert_deprecated do
ActionView::Template.register_template_handler :ruby_handler, :source.to_proc
end
assert_equal "3", @view.render(inline: "(1 + 2).to_s", type: :ruby_handler)
ensure
ActionView::Template.unregister_template_handler :ruby_handler