Erubis Users' Guide

6   Other Topics

6-1   Erubis::FastEruby Class

Erubis::FastEruby class generates more effective code than Erubis::Eruby.

fasteruby-example.rb
require 'erubis'
input = File.read('example.eruby')

puts "----- Erubis::Eruby -----"
print Erubis::Eruby.new(input).src

puts "----- Erubis::FastEruby -----"
print Erubis::FastEruby.new(input).src
result
$ ruby fasteruby-example.rb
----- Erubis::Eruby -----
_buf = ''; _buf << '<div>
'; for item in list 
 _buf << '  <p>'; _buf << ( item ).to_s; _buf << '</p>
  <p>'; _buf << Erubis::XmlHelper.escape_xml( item ); _buf << '</p>
'; end 
 _buf << '</div>
';
_buf.to_s
----- Erubis::FastEruby -----
_buf = ''; _buf << %Q`<div>\n`
 for item in list 
 _buf << %Q`  <p>#{ item }</p>
  <p>#{Erubis::XmlHelper.escape_xml( item )}</p>\n`
 end 
 _buf << %Q`</div>\n`
_buf.to_s

Technically, Erubis::FastEruby is just a subclass of Erubis::Eruby and includes InterpolationEnhancer. Erubis::FastEruby is faster than Erubis::Eruby but is not extensible compared to Erubis::Eruby. This is the reason why Erubis::FastEruby is not the default class of Erubis.

6-2   :bufvar Option

Since 2.7.0, Erubis supports :bufvar option which allows you to change buffer variable name (default '_buf').

bufvar-example.rb
require 'erubis'
input = File.read('example.eruby')

puts "----- default -----"
eruby = Erubis::FastEruby.new(input)
puts eruby.src

puts "----- with :bufvar option -----"
eruby = Erubis::FastEruby.new(input, :bufvar=>'@_out_buf')
print eruby.src
result
$ ruby bufvar-example.rb
----- default -----
_buf = ''; _buf << %Q`<div>\n`
 for item in list 
 _buf << %Q`  <p>#{ item }</p>
  <p>#{Erubis::XmlHelper.escape_xml( item )}</p>\n`
 end 
 _buf << %Q`</div>\n`
_buf.to_s
----- with :bufvar option -----
@_out_buf = ''; @_out_buf << %Q`<div>\n`
 for item in list 
 @_out_buf << %Q`  <p>#{ item }</p>
  <p>#{Erubis::XmlHelper.escape_xml( item )}</p>\n`
 end 
 @_out_buf << %Q`</div>\n`
@_out_buf.to_s

6-3   '<%= =%>' 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-4   '<%% %>' 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-5   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.

  • It is not able to specify variables to use. Using binding() method, all of local variables are passed to templates.
  • Changing local variables in templates may affect to varialbes in main program. If you assign '10' to local variable 'x' in templates, it may change variable 'x' in main program unintendedly.

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-6   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-7   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-8   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-9   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-10   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-11   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.7.0/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.7.0/
    $ 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-12   Helper CGI Script for Apache

Erubis provides helper CGI script for Apache. Using this script, it is very easy to publish *.rhtml files as *.html.

### install Erubis
$ tar xzf erubis-X.X.X.tar.gz
$ cd erubis-X.X.X/
$ ruby setup.py install
### copy files to ~/public_html
$ mkdir -p ~/public_html
$ cp public_html/_htaccess   ~/public_html/.htaccess
$ cp public_html/index.cgi   ~/public_html/
$ cp public_html/index.rhtml ~/public_html/
### add executable permission to index.cgi
$ chmod a+x ~/public_html/index.cgi
### edit .htaccess
$ vi ~/public_html/.htaccess
### (optional) edit index.cgi to configure
$ vi ~/public_html/index.cgi

Edit ~/public_html/.htaccess and modify user name.

~/public_html/.htaccess
## enable mod_rewrie
RewriteEngine on
## deny access to *.rhtml and *.cache
#RewriteRule \.(rhtml|cache)$ - [R=404,L]
RewriteRule \.(rhtml|cache)$ - [F,L]
## rewrite only if requested file is not found
RewriteCond %{SCRIPT_FILENAME} !-f
## handle request to *.html and directories by index.cgi
RewriteRule (\.html|/|^)$ /~username/index.cgi
#RewriteRule (\.html|/|^)$ index.cgi

After these steps, *.rhtml will be published as *.html. For example, if you access to http://host.domain/~username/index.html (or http://host.domain/~username/), file ~/public_html/index.rhtml will be displayed.

6-13   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-14   Benchmark

A benchmark script is included in Erubis package at 'erubis-2.7.0/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.7.0/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...

  • Erubis::Eruby runs more than 10 percent faster than eruby.
  • Erubis::Eruby runs about 3 times faster than ERB.
  • Caching (by Erubis::Eruby.load_file()) makes Erubis about 40-50 percent faster.
  • Erubis::FastEruby is a litte faster than Erubis::Eruby.
  • Array buffer (ArrayBufferEnhancer) is a little slower than string buffer (StringBufferEnhancer which Erubis::Eruby includes)
  • $stdout and print() make Erubis a little slower.
  • Erubis::TinyEruby (at 'erubis/tiny.rb') is the fastest in all eRuby implementations when no caching.

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)