Change ActionView ERB Handler from Erubis to Erubi

Erubi offers the following advantages for Rails:

* Works with ruby's --enable-frozen-string-literal option
* Has 88% smaller memory footprint
* Does no freedom patching (Erubis adds a method to Kernel)
* Has simpler internals (1 file, <150 lines of code)
* Has an open development model (Erubis doesn't have a
  public source control repository or bug tracker)
* Is not dead (Erubis hasn't been updated since 2011)

Erubi is a simplified fork of Erubis that contains just the
parts that are generally needed (which includes the parts
that Rails uses).  The only intentional difference in
behavior is that it does not include support for <%=== tags
for debug output.  That could be added to the ActionView ERB
handler if it is desired.

The Erubis template handler remains in a deprecated state
so that code that accesses it directly does not break.  It
can be removed after Rails 5.1.
This commit is contained in:
Jeremy Evans 2017-01-20 15:02:34 -08:00 committed by Jeremy Daer
parent 72a869e626
commit 7da8d76206
No known key found for this signature in database
GPG Key ID: AB8F6399D5C60664
13 changed files with 214 additions and 81 deletions

View File

@ -51,6 +51,9 @@ gem "dalli", ">= 2.2.1"
gem "listen", ">= 3.0.5", "< 3.2", require: false
gem "libxml-ruby", platforms: :ruby
# Action View. For testing Erubis handler deprecation.
gem "erubis", "~> 2.7.0", require: false
# Active Job.
group :job do
gem "resque", github: "resque/resque", require: false

View File

@ -78,7 +78,7 @@ PATH
actionview (5.1.0.alpha)
activesupport (= 5.1.0.alpha)
builder (~> 3.1)
erubis (~> 2.7.0)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
activejob (5.1.0.alpha)
@ -178,6 +178,7 @@ GEM
http_parser.rb (>= 0.6.0)
em-socksify (0.3.1)
eventmachine (>= 1.0.0.beta.4)
erubi (1.4.0)
erubis (2.7.0)
event_emitter (0.2.5)
eventmachine (1.2.1)
@ -390,6 +391,7 @@ DEPENDENCIES
delayed_job!
delayed_job_active_record!
em-hiredis
erubis (~> 2.7.0)
hiredis
jquery-rails
json (>= 2.0.0)
@ -432,4 +434,4 @@ DEPENDENCIES
websocket-client-simple!
BUNDLED WITH
1.13.7
1.14.3

View File

@ -1,4 +1,3 @@
require "erubis"
require "abstract_controller/error"
require "active_support/configurable"
require "active_support/descendants_tracker"
@ -22,7 +21,6 @@ module AbstractController
include ActiveSupport::Configurable
extend ActiveSupport::DescendantsTracker
undef_method :not_implemented
class << self
attr_reader :abstract
alias_method :abstract?, :abstract

View File

@ -1,3 +1,17 @@
* Change the ERB handler from Erubis to Erubi.
Erubi is an Erubis fork that's svelte, simple, and currently maintained.
Plus it supports `--enable-frozen-string-literal` in Ruby 2.3+.
Compatibility: Drops support for `<%===` tags for debug output.
These were an unused, undocumented side effect of the Erubis
implementation.
Deprecation: The Erubis handler will be removed in Rails 5.2, for the
handful of folks using it directly.
*Jeremy Evans*
* Allow render locals to be assigned to instance variables in a view.
Fixes #27480.

View File

@ -22,7 +22,7 @@ Gem::Specification.new do |s|
s.add_dependency "activesupport", version
s.add_dependency "builder", "~> 3.1"
s.add_dependency "erubis", "~> 2.7.0"
s.add_dependency "erubi", "~> 1.4"
s.add_dependency "rails-html-sanitizer", "~> 1.0", ">= 1.0.2"
s.add_dependency "rails-dom-testing", "~> 2.0"

View File

@ -11,7 +11,7 @@ module ActionView #:nodoc:
# = Action View Base
#
# Action View templates can be written in several ways.
# If the template file has a <tt>.erb</tt> extension, then it uses the erubis[https://rubygems.org/gems/erubis]
# If the template file has a <tt>.erb</tt> extension, then it uses the erubi[https://rubygems.org/gems/erubi]
# template system which can embed Ruby into an HTML document.
# If the template file has a <tt>.builder</tt> extension, then Jim Weirich's Builder::XmlMarkup library is used.
#

View File

@ -1,79 +1,12 @@
require "erubis"
module ActionView
class Template
module Handlers
class Erubis < ::Erubis::Eruby
def add_preamble(src)
@newline_pending = 0
src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
end
def add_text(src, text)
return if text.empty?
if text == "\n"
@newline_pending += 1
else
src << "@output_buffer.safe_append='"
src << "\n" * @newline_pending if @newline_pending > 0
src << escape_text(text)
src << "'.freeze;"
@newline_pending = 0
end
end
# Erubis toggles <%= and <%== behavior when escaping is enabled.
# We override to always treat <%== as escaped.
def add_expr(src, code, indicator)
case indicator
when "=="
add_expr_escaped(src, code)
else
super
end
end
BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
def add_expr_literal(src, code)
flush_newline_if_pending(src)
if BLOCK_EXPR.match?(code)
src << "@output_buffer.append= " << code
else
src << "@output_buffer.append=(" << code << ");"
end
end
def add_expr_escaped(src, code)
flush_newline_if_pending(src)
if BLOCK_EXPR.match?(code)
src << "@output_buffer.safe_expr_append= " << code
else
src << "@output_buffer.safe_expr_append=(" << code << ");"
end
end
def add_stmt(src, code)
flush_newline_if_pending(src)
super
end
def add_postamble(src)
flush_newline_if_pending(src)
src << "@output_buffer.to_s"
end
def flush_newline_if_pending(src)
if @newline_pending > 0
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
@newline_pending = 0
end
end
end
Erubis = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("Erubis", "ActionView::Template::Handlers::ERB::Erubis", message: "ActionView::Template::Handlers::Erubis is deprecated and will be removed from Rails 5.2. Switch to ActionView::Template::Handlers::ERB::Erubi instead.")
class ERB
autoload :Erubi, "action_view/template/handlers/erb/erubi"
autoload :Erubis, "action_view/template/handlers/erb/erubis"
# Specify trim mode for the ERB compiler. Defaults to '-'.
# See ERB documentation for suitable values.
class_attribute :erb_trim_mode
@ -81,7 +14,7 @@ module ActionView
# Default implementation used.
class_attribute :erb_implementation
self.erb_implementation = Erubis
self.erb_implementation = Erubi
# Do not escape templates of these mime types.
class_attribute :escape_whitelist

View File

@ -0,0 +1,81 @@
require "erubi"
module ActionView
class Template
module Handlers
class ERB
class Erubi < ::Erubi::Engine
# :nodoc: all
def initialize(input, properties = {})
@newline_pending = 0
# Dup properties so that we don't modify argument
properties = Hash[properties]
properties[:preamble] = "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
properties[:postamble] = "@output_buffer.to_s"
properties[:bufvar] = "@output_buffer"
properties[:escapefunc] = ""
super
end
def evaluate(action_view_erb_handler_context)
pr = eval("proc { #{@src} }", binding, @filename || "(erubi)")
action_view_erb_handler_context.instance_eval(&pr)
end
private
def add_text(text)
return if text.empty?
if text == "\n"
@newline_pending += 1
else
src << "@output_buffer.safe_append='"
src << "\n" * @newline_pending if @newline_pending > 0
src << text.gsub(/['\\]/, '\\\\\&')
src << "'.freeze;"
@newline_pending = 0
end
end
BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
def add_expression(indicator, code)
flush_newline_if_pending(src)
if (indicator == "==") || @escape
src << "@output_buffer.safe_expr_append="
else
src << "@output_buffer.append="
end
if BLOCK_EXPR.match?(code)
src << " " << code
else
src << "(" << code << ");"
end
end
def add_code(code)
flush_newline_if_pending(src)
super
end
def add_postamble(_)
flush_newline_if_pending(src)
super
end
def flush_newline_if_pending(src)
if @newline_pending > 0
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
@newline_pending = 0
end
end
end
end
end
end
end

View File

@ -0,0 +1,80 @@
require "erubis"
module ActionView
class Template
module Handlers
class ERB
class Erubis < ::Erubis::Eruby
# :nodoc: all
def add_preamble(src)
@newline_pending = 0
src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
end
def add_text(src, text)
return if text.empty?
if text == "\n"
@newline_pending += 1
else
src << "@output_buffer.safe_append='"
src << "\n" * @newline_pending if @newline_pending > 0
src << escape_text(text)
src << "'.freeze;"
@newline_pending = 0
end
end
# Erubis toggles <%= and <%== behavior when escaping is enabled.
# We override to always treat <%== as escaped.
def add_expr(src, code, indicator)
case indicator
when "=="
add_expr_escaped(src, code)
else
super
end
end
BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
def add_expr_literal(src, code)
flush_newline_if_pending(src)
if BLOCK_EXPR.match?(code)
src << "@output_buffer.append= " << code
else
src << "@output_buffer.append=(" << code << ");"
end
end
def add_expr_escaped(src, code)
flush_newline_if_pending(src)
if BLOCK_EXPR.match?(code)
src << "@output_buffer.safe_expr_append= " << code
else
src << "@output_buffer.safe_expr_append=(" << code << ");"
end
end
def add_stmt(src, code)
flush_newline_if_pending(src)
super
end
def add_postamble(src)
flush_newline_if_pending(src)
src << "@output_buffer.to_s"
end
def flush_newline_if_pending(src)
if @newline_pending > 0
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
@newline_pending = 0
end
end
end
end
end
end
end

View File

@ -0,0 +1,11 @@
require "abstract_unit"
module ERBTest
class DeprecatedErubisImplementationTest < ActionView::TestCase
test "Erubis implementation is deprecated" do
assert_deprecated "ActionView::Template::Handlers::Erubis is deprecated and will be removed from Rails 5.2. Switch to ActionView::Template::Handlers::ERB::Erubi instead." do
assert_equal "ActionView::Template::Handlers::ERB::Erubis", ActionView::Template::Handlers::Erubis.to_s
end
end
end
end

View File

@ -14,7 +14,7 @@ module ERBTest
class BlockTestCase < ActiveSupport::TestCase
def render_content(start, inside)
template = block_helper(start, inside)
ActionView::Template::Handlers::Erubis.new(template).evaluate(ViewContext.new)
ActionView::Template::Handlers::ERB.erb_implementation.new(template).evaluate(ViewContext.new)
end
def block_helper(str, rest)

View File

@ -122,10 +122,11 @@ module ActiveSupport
# (Backtrace information…)
# ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
class DeprecatedConstantProxy < DeprecationProxy
def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance)
def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance, message: "#{old_const} is deprecated! Use #{new_const} instead.")
@old_const = old_const
@new_const = new_const
@deprecator = deprecator
@message = message
end
# Returns the class of the new constant.
@ -143,7 +144,7 @@ module ActiveSupport
end
def warn(callstack, called, args)
@deprecator.warn("#{@old_const} is deprecated! Use #{@new_const} instead.", callstack)
@deprecator.warn(@message, callstack)
end
end
end

View File

@ -285,6 +285,16 @@ class DeprecationTest < ActiveSupport::TestCase
end
end
def test_deprecated_constant_with_custom_message
deprecator = deprecator_with_messages
klass = Class.new
klass.const_set(:OLD, ActiveSupport::Deprecation::DeprecatedConstantProxy.new("klass::OLD", "Object", deprecator, message: "foo"))
klass::OLD.to_s
assert_match "foo", deprecator.messages.last
end
def test_deprecated_instance_variable_with_instance_deprecator
deprecator = deprecator_with_messages