Skip to content

Commit

Permalink
Pass source to template handler and deprecate old style handler
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
tenderlove committed Feb 2, 2019
1 parent 2169bd3 commit 28f88e0
Show file tree
Hide file tree
Showing 10 changed files with 55 additions and 21 deletions.
3 changes: 1 addition & 2 deletions actionview/lib/action_view/template.rb
Expand Up @@ -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"

Expand Down Expand Up @@ -304,7 +303,7 @@ def initialize(template, source)
# 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
Expand Down
28 changes: 27 additions & 1 deletion actionview/lib/action_view/template/handlers.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require "active_support/deprecation"

module ActionView #:nodoc:
# = Action View Template Handlers
class Template #:nodoc:
Expand All @@ -14,7 +16,7 @@ def self.extended(base)
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 = {}
Expand All @@ -24,11 +26,35 @@ def self.extensions
@@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
Expand Down
4 changes: 2 additions & 2 deletions actionview/lib/action_view/template/handlers/builder.rb
Expand Up @@ -5,11 +5,11 @@ module Template::Handlers
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

Expand Down
10 changes: 5 additions & 5 deletions actionview/lib/action_view/template/handlers/erb.rb
Expand Up @@ -28,8 +28,8 @@ class ERB

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?
Expand All @@ -40,17 +40,17 @@ def handles_encoding?
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!
Expand Down
2 changes: 1 addition & 1 deletion actionview/lib/action_view/template/handlers/html.rb
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions actionview/lib/action_view/template/handlers/raw.rb
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion actionview/test/abstract_unit.rb
Expand Up @@ -58,7 +58,7 @@ def render_erb(string)
template = ActionView::Template.new(
string.strip,
"test template",
ActionView::Template::Handlers::ERB,
ActionView::Template.handler_for_extension(:erb),
{})

template.render(ActionView::Base.empty, {}).strip
Expand Down
4 changes: 2 additions & 2 deletions actionview/test/actionpack/controller/layout_test.rb
Expand Up @@ -70,7 +70,7 @@ def test_controller_name_layout_name_match
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
Expand Down Expand Up @@ -212,7 +212,7 @@ def test_layout_except_exception_when_excepted
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"
Expand Down
4 changes: 2 additions & 2 deletions actionview/test/template/dependency_tracker_test.rb
Expand Up @@ -17,8 +17,8 @@ def initialize(source, handler = Neckbeard)
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
Expand Down
15 changes: 12 additions & 3 deletions actionview/test/template/render_test.rb
Expand Up @@ -450,13 +450,22 @@ def test_render_fallbacks_to_erb_for_unknown_types
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
Expand Down

2 comments on commit 28f88e0

@richjdsmith
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @tenderlove, do you mind explaining these changes to me? I'm struggling to understand the code, and simply changing calls to the ActionView::Template.registered_template_handler(:erb).call() method from erb.call(template) to erb.call(template, template.source) is not working for me.

@tenderlove
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Template source should be immutable, so rather than depending on the handler to mutate it, we pass the template source to the handler. Can you give more details? I don't understand what isn't working for you.

Thanks!

Please sign in to comment.