Issue #13839 has been updated by jeremyevans0 (Jeremy Evans).


It might be better to compare this to Erubi, the current default ERB template processor in Rails and Tilt.  While this approach is fast for small strings, it's actually slower for large strings (probably due to the use of `+=` instead of `<<`).  Additionally, in most cases when you are using templates, you want to cache the resulting ruby code so that repeated rendering does not need to reparse the template input.  When you start caching the template object, the performance advantage disappears for small strings, and the performance disadvantage becomes larger for large strings.

I don't think ruby should include this this in core or stdlib.  I think it would be best to have it as an gem, possibly integrating with Tilt.  But I'll admit I'm biased in this respect, as I'm the maintainer of Erubi.

Here's example benchmark code:

~~~ ruby
class Template

  attr_reader :input

  def initialize(input)
    @input = input
  end

  def output
    "output = %\0" + @input.gsub("{%", "\0\n").gsub("%}", "\noutput += %\0") + "\0"
  end

  def render(binding)
    eval(output, binding)
  end

end

require 'erubi'

class ErubiTemplate
  attr_reader :input

  def initialize(input)
    @input = input
    @src = Erubi::Engine.new(input).src
  end

  def render(binding)
    eval(@src, binding)
  end
end

template_input = <<'END'
{% if true %}
  Hello #{"World"}
{% end %}
END

erubi_input = <<'END'
<% if true %>
  Hello <%= "World" %>
<% end %>
END

require 'benchmark/ips'

Benchmark.ips do |x|
  x.report("Template-small"){Template.new(template_input).render(binding)}
  x.report("Erubi-small"){ErubiTemplate.new(erubi_input).render(binding)}
  x.compare!
end

template = Template.new(template_input)
erubi = ErubiTemplate.new(erubi_input)

Benchmark.ips do |x|
  x.report("Template-small-cache"){template.render(binding)}
  x.report("Erubi-small-cache"){erubi.render(binding)}
  x.compare!
end

template_input *= 10000
erubi_input *= 10000

Benchmark.ips do |x|
  x.report("Template-large"){Template.new(template_input).render(binding)}
  x.report("Erubi-large"){ErubiTemplate.new(erubi_input).render(binding)}
  x.compare!
end

template = Template.new(template_input)
erubi = ErubiTemplate.new(erubi_input)

Benchmark.ips do |x|
  x.report("Template-large-cache"){template.render(binding)}
  x.report("Erubi-large-cache"){erubi.render(binding)}
  x.compare!
end
~~~

and results (slightly reformatted for easier viewing):

~~~
Calculating -------------------------------------
      Template-small     19.271k (_ 0.7%) i/s -     97.515k in   5.060508s
         Erubi-small      9.123k (_ 0.4%) i/s -     46.163k in   5.060080s

Comparison:
      Template-small:    19270.8 i/s
         Erubi-small:     9123.2 i/s - 2.11x  slower


Calculating -------------------------------------
Template-small-cache     19.962k (_ 0.5%) i/s -    100.980k in   5.058694s
   Erubi-small-cache     21.568k (_ 0.5%) i/s -    108.900k in   5.049226s

Comparison:
   Erubi-small-cache:    21568.2 i/s
Template-small-cache:    19962.1 i/s - 1.08x  slower


Calculating -------------------------------------
      Template-large      0.451  (_ 0.0%) i/s -      3.000  in   6.658039s
         Erubi-large      0.693  (_ 0.0%) i/s -      4.000  in   5.771604s

Comparison:
         Erubi-large:        0.7 i/s
      Template-large:        0.5 i/s - 1.54x  slower


Calculating -------------------------------------
Template-large-cache      0.449  (_ 0.0%) i/s -      3.000  in   6.682826s
   Erubi-large-cache      3.366  (_ 0.0%) i/s -     17.000  in   5.062812s

Comparison:
   Erubi-large-cache:        3.4 i/s
Template-large-cache:        0.4 i/s - 7.50x  slower
~~~

----------------------------------------
Feature #13839: String Interpolation Statements
https://bugs.ruby-lang.org/issues/13839#change-66283

* Author: se8 (Sbastien Durand)
* Status: Open
* Priority: Normal
* Assignee: 
* Target version: 
----------------------------------------
Hello!


Here is a KISS implementation of a template engine in Ruby:

~~~ ruby
class Template

  attr_reader :input

  def initialize(input)
    @input = input
  end

  def output
    "output = %\0" + @input.gsub("{%", "\0\n").gsub("%}", "\noutput += %\0") + "\0"
  end

  def render(binding)
    eval(output, binding)
  end

end
~~~



Usage:

~~~ text
{% if true %}
  Hello #{'World'}
{% end %}

Template.new('...').render(binding)
~~~



It's kind of a hack on top of Ruby string interpolation, so it's hell fast (~4 times faster than ERB).

Could it be a good idea to implement this kind of statements directly in Ruby string interpolation? Maybe a syntax like that:

~~~ text
"%{3.times do}Hello #{'World'}%{end}"
~~~

So Ruby would have a fast minimal native template engine, with #{expressions} and %{statements}:

~~~ text
eval(File.read("..."), binding)
~~~



-- 
https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request / ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>