Erubis Users' Guide

6   Other Topics

6-1   '<%= =%>' and '<%= -%>'

Since 2.6.0, '<%= -%>' remove tail spaces and newline. This is for compatibiliy with ERB when trim mode is '-'. '<%= =%>' also removes tail spaces and newlines, and this is Erubis-original enhancement (cooler than '<%= -%>', isn't it?).

tailnewline.rhtml
<div>
<%= @var -%>          # or <%= @var =%>
</div>
result (version 2.5.0):
$ erubis -c '{var: "AAA\n"}' tailnewline.rhtml
<div>
AAA

</div>
result (version 2.6.0):
$ erubis -c '{var: "AAA\n"}' tailnewline.rhtml
<div>
AAA
</div>

6-2   '<%% %>' and '<%%= %>'

Since 2.6.0, '<%% %>' and '<%%= %>' are converted into '<% %>' and '<%= %>' respectively. This is for compatibility with ERB.

doublepercent.rhtml:
<ul>
<%% for item in @list %>
  <li><%%= item %></li>
<%% end %>
</ul>
result:
$ erubis doublepercent.rhtml
<ul>
<% for item in @list %>
  <li><%= item %></li>
<% end %>
</ul>

6-3   evaluate(context) v.s. result(binding)

It is recommended to use 'Erubis::Eruby#evaluate(context)' instead of 'Erubis::Eruby#result(binding)' because Ruby's Binding object has some problems.

The following example shows that assignment of some values into variable 'x' in templates affect to local variable 'x' in main program unintendedly.

template1.rhtml (intended to be passed 'items' from main program)
<% for x in items %>
item = <%= x %>
<% end %>
** debug: local variables=<%= local_variables().inspect() %>
main_program1.rb (intended to pass 'items' to template)
require 'erubis'
eruby = Erubis::Eruby.new(File.read('template1.rhtml'))
items = ['foo', 'bar', 'baz']
x = 1
## local variable 'x' and 'eruby' are passed to template as well as 'items'!
print eruby.result(binding())    
## local variable 'x' is changed unintendedly because it is changed in template!
puts "** debug: x=#{x.inspect}"  #=> "baz"
Result:
$ ruby main_program1.rb
item = foo
item = bar
item = baz
** debug: local variables=["eruby", "items", "x", "_buf"]
** debug: x="baz"

This problem is caused because Ruby's Binding class is poor to use in template engine. Binding class should support the following features.

b = Binding.new     # create empty Binding object
b['x'] = 1          # set local variables using binding object

But the above features are not implemented in Ruby.

A pragmatic solution is to use 'Erubis::Eruby#evaluate(context)' instead of 'Erubis::Eruby#result(binding)'. 'evaluate(context)' uses Erubis::Context object and instance variables instead of Binding object and local variables.

template2.rhtml (intended to be passed '@items' from main program)
<% for x in @items %>
item = <%= x %>
<% end %>
** debug: local variables=<%= local_variables().inspect() %>
main_program2.rb (intended to pass '@items' to template)
require 'erubis'
eruby = Erubis::Eruby.new(File.read('template2.rhtml'))
items = ['foo', 'bar', 'baz']
x = 1
## only 'items' are passed to template
print eruby.evaluate(:items=>items)    
## local variable 'x' is not changed!
puts "** debug: x=#{x.inspect}"  #=> 1
Result:
$ ruby main_program2.rb
item = foo
item = bar
item = baz
** debug: local variables=["_context", "x", "_buf"]
** debug: x=1

6-4   Class Erubis::FastEruby

[experimental]

Erubis provides Erubis::FastEruby class which includes InterpolationEnhancer and works faster than Erubis::Eruby class. If you desire more speed, try Erubis::FastEruby class.

File 'fasteruby.rhtml':
<html>
  <body>
    <h1><%== @title %></h1>
    <table>
<% i = 0 %>
<% for item in @list %>
<%   i += 1 %>
      <tr>
        <td><%= i %></td>
        <td><%== item %></td>
      </tr>
<% end %>
    </table>
  </body>
</html>
File 'fasteruby.rb':
require 'erubis'
input = File.read('fasteruby.rhtml')
eruby = Erubis::FastEruby.new(input)    # create Eruby object

puts "---------- script source ---"
puts eruby.src

puts "---------- result ----------"
context = { :title=>'Example', :list=>['aaa', 'bbb', 'ccc'] }
output = eruby.evaluate(context)
print output
output
$ ruby fasteruby.rb
---------- script source ---
_buf = ''; _buf << %Q`<html>
  <body>
    <h1>#{Erubis::XmlHelper.escape_xml( @title )}</h1>
    <table>\n`
 i = 0 
 for item in @list 
   i += 1 
 _buf << %Q`      <tr>
        <td>#{ i }</td>
        <td>#{Erubis::XmlHelper.escape_xml( item )}</td>
      </tr>\n`
 end 
 _buf << %Q`    </table>
  </body>
</html>\n`
_buf.to_s
---------- result ----------
<html>
  <body>
    <h1>Example</h1>
    <table>
      <tr>
        <td>1</td>
        <td>aaa</td>
      </tr>
      <tr>
        <td>2</td>
        <td>bbb</td>
      </tr>
      <tr>
        <td>3</td>
        <td>ccc</td>
      </tr>
    </table>
  </body>
</html>

6-5   Syntax Checking

Command-line option '-z' checks syntax. It is similar to 'erubis -x file.rhtml | ruby -wc', but it can take several file names.

example of command-line option '-z'
$ erubis -z app/views/*/*.rhtml
Syntax OK

6-6   File Caching

Erubis::Eruby.load_file(filename) convert file into Ruby script and return Eruby object. In addition, it caches converted Ruby script into cache file (filename + '.cache') if cache file is old or not exist. If cache file exists and is newer than eruby file, Erubis::Eruby.load_file() loads cache file.

example of Erubis::Eruby.load_file()
require 'erubis'
filename = 'example.rhtml'
eruby = Erubis::Eruby.load_file(filename)
cachename = filename + '.cache'
if test(?f, cachename)
  puts "*** cache file '#{cachename}' created."
end

Since 2.6.0, it is able to specify cache filename.

specify cache filename.
filename = 'example.rhtml'
eruby = Erubis::Eruby.load_file(filename, :cachename=>filename+'.cache')

Caching makes Erubis about 40-50 percent faster than no-caching. See benchmark for details.

6-7   Erubis::TinyEruby class

Erubis::TinyEruby class in 'erubis/tiny.rb' is the smallest implementation of eRuby. If you don't need any enhancements of Erubis and only require simple eRuby implementation, try Erubis::TinyEruby class.

6-8   NoTextEnhancer and NoCodeEnhancer in PHP

NoTextEnhancer and NoCodEnahncer are quite useful not only for eRuby but also for PHP. The former "drops" HTML text and show up embedded Ruby/PHP code and the latter drops embedded Ruby/PHP code and leave HTML text.

For example, see the following PHP script.

notext-example.php
<html>
  <body>
    <h3>List</h3>
    <?php if (!$list || count($list) == 0) { ?>
    <p>not found.</p>
    <?php } else { ?>
    <table>
      <tbody>
        <?php $i = 0; ?>
        <?php foreach ($list as $item) { ?>
        <tr bgcolor="<?php echo ++$i % 2 == 1 ? '#FFCCCC' : '#CCCCFF'; ?>">
          <td><?php echo $item; ?></td>
        </tr>
        <?php } ?>
      </tbody>
    </table>
    <?php } ?>
  </body>
</html>

This is complex because PHP code and HTML document are mixed. NoTextEnhancer can separate PHP code from HTML document.

example of using NoTextEnhancer with PHP file
$ erubis -l php --pi=php -N -E NoText --trim=false notext-example.php
    1:  
    2:  
    3:  
    4:      <?php if (!$list || count($list) == 0) { ?>
    5:  
    6:      <?php } else { ?>
    7:  
    8:  
    9:          <?php $i = 0; ?>
   10:          <?php foreach ($list as $item) { ?>
   11:                       <?php echo ++$i % 2 == 1 ? '#FFCCCC' : '#CCCCFF'; ?>
   12:                <?php echo $item; ?>
   13:  
   14:          <?php } ?>
   15:  
   16:  
   17:      <?php } ?>
   18:  
   19:  

In the same way, NoCodeEnhancer can extract HTML tags.

example of using NoCodeEnhancer with PHP file
$ erubis -l php --pi=php -N -E NoCode --trim=false notext-example.php
    1:  <html>
    2:    <body>
    3:      <h3>List</h3>
    4:      
    5:      <p>not found.</p>
    6:      
    7:      <table>
    8:        <tbody>
    9:          
   10:          
   11:          <tr bgcolor="">
   12:            <td></td>
   13:          </tr>
   14:          
   15:        </tbody>
   16:      </table>
   17:      
   18:    </body>
   19:  </html>

6-9   Helper Class for mod_ruby

Thanks Andrew R Jackson, he developed 'erubis-run.rb' which enables you to use Erubis with mod_ruby.

  1. Copy 'erubis-2.6.2/contrib/erubis-run.rb' to the 'RUBYLIBDIR/apache' directory (for example '/usr/local/lib/ruby/1.8/apache') which contains 'ruby-run.rb', 'eruby-run.rb', and so on.
    $ cd erubis-2.6.2/
    $ sudo copy contrib/erubis-run.rb /usr/local/lib/ruby/1.8/apache/
    
  2. Add the following example to your 'httpd.conf' (for example '/usr/local/apache2/conf/httpd.conf')
    LoadModule ruby_module modules/mod_ruby.so
    <IfModule mod_ruby.c>
      RubyRequire apache/ruby-run
      RubyRequire apache/eruby-run
      RubyRequire apache/erubis-run
      <Location /erubis>
        SetHandler ruby-object
        RubyHandler Apache::ErubisRun.instance
      </Location>
      <Files *.rhtml>
        SetHandler ruby-object
        RubyHandler Apache::ErubisRun.instance
      </Files>
    </IfModule>
    
  3. Restart Apache web server.
    $ sudo /usr/local/apache2/bin/apachectl stop
    $ sudo /usr/local/apache2/bin/apachectl start
    
  4. Create *.rhtml file, for example:
    <html>
     <body>
      Now is <%= Time.now %>
      Erubis version is <%= Erubis::VERSION %>
     </body>
    </html>
    
  5. Change mode of your directory to be writable by web server process.
    $ cd /usr/local/apache2/htdocs/erubis
    $ sudo chgrp daemon .
    $ sudo chmod 775 .
    
  6. Access the *.rhtml file and you'll get the web page.

You must set your directories to be writable by web server process, because Apache::ErubisRun calls Erubis::Eruby.load_file() internally which creates cache files in the same directory in which '*.rhtml' file exists.

6-10   Define method

Erubis::Eruby#def_method() defines instance method or singleton method.

require 'erubis'
s = "hello <%= name %>"
eruby = Erubis::Eruby.new(s)
filename = 'hello.rhtml'

## define instance method to Dummy class (or module)
class Dummy; end
eruby.def_method(Dummy, 'render(name)', filename)  # filename is optional
p Dummy.new.render('world')    #=> "hello world"

## define singleton method to dummy object
obj = Object.new
eruby.def_method(obj, 'render(name)', filename)    # filename is optional
p obj.render('world')          #=> "hello world"

6-11   Benchmark

A benchmark script is included in Erubis package at 'erubis-2.6.2/benchark/' directory. Here is an example result of benchmark.

MacOS X 10.4 Tiger, Intel CoreDuo 1.83GHz, Ruby1.8.6, eruby1.0.5, gcc4.0.1
$ cd erubis-2.6.2/benchmark/
$ ruby bench.rb -n 10000 -m execute
*** ntimes=10000, testmode=execute
                                    user     system      total        real
eruby                          12.720000   0.240000  12.960000 ( 12.971888)
ERB                            36.760000   0.350000  37.110000 ( 37.112019)
ERB(cached)                    11.990000   0.440000  12.430000 ( 12.430375)
Erubis::Eruby                  10.840000   0.300000  11.140000 ( 11.144426)
Erubis::Eruby(cached)           7.540000   0.410000   7.950000 (  7.969305)
Erubis::FastEruby              10.440000   0.300000  10.740000 ( 10.737808)
Erubis::FastEruby(cached)       6.940000   0.410000   7.350000 (  7.353666)
Erubis::TinyEruby               9.550000   0.290000   9.840000 (  9.851729)
Erubis::ArrayBufferEruby       11.010000   0.300000  11.310000 ( 11.314339)
Erubis::PrintOutEruby          11.640000   0.290000  11.930000 ( 11.942141)
Erubis::StdoutEruby            11.590000   0.300000  11.890000 ( 11.886512)

This shows that...

Escaping HTML characters (such as '< > & "') makes Erubis more faster than eruby and ERB, because Erubis::XmlHelper#escape_xml() works faster than CGI.escapeHTML() and ERB::Util#h(). The following shows that Erubis runs more than 40 percent (when no-cached) or 90 percent (when cached) faster than eruby if HTML characters are escaped.

When escaping HTML characters with option '-e'
$ ruby bench.rb -n 10000 -m execute -ep
*** ntimes=10000, testmode=execute
                                    user     system      total        real
eruby                          21.700000   0.290000  21.990000 ( 22.050687)
ERB                            45.140000   0.390000  45.530000 ( 45.536976)
ERB(cached)                    20.340000   0.470000  20.810000 ( 20.822653)
Erubis::Eruby                  14.830000   0.310000  15.140000 ( 15.147930)
Erubis::Eruby(cached)          11.090000   0.420000  11.510000 ( 11.514954)
Erubis::FastEruby              14.850000   0.310000  15.160000 ( 15.172499)
Erubis::FastEruby(cached)      10.970000   0.430000  11.400000 ( 11.399605)
Erubis::ArrayBufferEruby       14.970000   0.300000  15.270000 ( 15.281061)
Erubis::PrintOutEruby          15.780000   0.300000  16.080000 ( 16.088289)
Erubis::StdoutEruby            15.840000   0.310000  16.150000 ( 16.235338)