Overview
ERB (Embedded Ruby) is Ruby's standard templating system that embeds Ruby code within text files to generate dynamic content. ERB processes templates by executing embedded Ruby expressions and statements, replacing them with their output while preserving the surrounding text structure.
The core ERB class provides template compilation and rendering capabilities. ERB templates contain three types of embedded Ruby constructs: <%= %>
for output expressions, <% %>
for non-output statements, and <%# %>
for comments. The ERB processor evaluates these constructs within a specified binding context, giving access to variables and methods from the calling scope.
ERB serves as the foundation for view templates in Rails and other web frameworks, but functions equally well for generating configuration files, reports, email templates, or any text-based content requiring dynamic generation.
require 'erb'
template = ERB.new("Hello, <%= name %>! Today is <%= Date.today %>.")
result = template.result(binding)
# Assumes 'name' variable exists in current scope
# Template with logic
template_string = <<~ERB
<% users.each do |user| %>
User: <%= user.name %> (<%= user.email %>)
<% end %>
ERB
erb = ERB.new(template_string)
users = [
OpenStruct.new(name: "Alice", email: "alice@example.com"),
OpenStruct.new(name: "Bob", email: "bob@example.com")
]
puts erb.result(binding)
# Template from file
template = ERB.new(File.read('template.erb'))
data = { title: "Report", items: [1, 2, 3] }
output = template.result_with_hash(data)
Basic Usage
ERB templates combine static text with embedded Ruby code. The ERB.new
constructor accepts a template string and optional parameters for safe mode and trim settings. The resulting ERB object compiles the template for repeated execution with different data contexts.
The result
method executes the compiled template within a binding context. Pass binding
to use the current scope's variables and methods, or create custom binding objects for isolated template execution. The result_with_hash
method provides convenient hash-based variable passing without requiring explicit binding creation.
Template syntax supports three embedding types. Output expressions <%= expression %>
evaluate the Ruby expression and include its string representation in the output. Statement blocks <% statement %>
execute Ruby code without generating output, useful for control structures and variable assignments. Comments <%# comment %>
document templates without appearing in rendered output.
class TemplateRenderer
def initialize(template_string)
@erb = ERB.new(template_string, trim_mode: '-')
end
def render(variables = {})
# Create isolated binding with variables
template_binding = create_binding(variables)
@erb.result(template_binding)
end
private
def create_binding(variables)
binding.tap do |b|
variables.each { |key, value| b.local_variable_set(key, value) }
end
end
end
ERB supports trim modes to control whitespace handling. The dash trim mode (-
) removes trailing newlines from lines ending with -%>
. This prevents unwanted blank lines in generated output, particularly important for code generation or structured text formats.
# Whitespace control example
template = <<~ERB
<% items.each do |item| -%>
Item: <%= item %>
<% end -%>
ERB
erb = ERB.new(template, trim_mode: '-')
items = ['apple', 'banana', 'cherry']
puts erb.result(binding)
# Produces clean output without extra blank lines
The safe mode parameter controls code evaluation restrictions. Safe mode levels 1-4 progressively restrict dangerous operations like eval
, system calls
, and file access. Safe mode prevents template injection attacks when processing untrusted template content.
# Safe mode prevents dangerous operations
unsafe_template = "<%= system('rm -rf /') %>"
safe_erb = ERB.new(unsafe_template, safe_level: 4)
# Raises SecurityError when executed
ERB integrates seamlessly with object contexts through binding manipulation. Define template helper methods in classes or modules, then include them in the binding context for template access.
module TemplateHelpers
def format_currency(amount)
"$#{sprintf('%.2f', amount)}"
end
def html_escape(text)
text.gsub(/[&<>"']/) { |char|
case char
when '&' then '&'
when '<' then '<'
when '>' then '>'
when '"' then '"'
when "'" then '''
end
}
end
end
class InvoiceGenerator
include TemplateHelpers
def generate_invoice(invoice_data)
template = ERB.new(File.read('invoice_template.erb'))
template.result(binding)
end
end
Error Handling & Debugging
ERB template errors fall into several categories: syntax errors during template compilation, runtime errors during template execution, and encoding errors when processing text with mixed character sets. Each error type requires different debugging approaches and recovery strategies.
Syntax errors occur when ERB cannot parse embedded Ruby code. These errors manifest during ERB object instantiation or template compilation. ERB provides line number information, but line numbers reflect the template structure, not the original file if templates span multiple lines.
class ERBDebugger
def self.compile_with_errors(template_string, template_name = 'template')
erb = ERB.new(template_string)
erb.def_method(Object, :debug_render, template_name)
true
rescue SyntaxError => e
puts "Template syntax error in #{template_name}:"
puts "Line: #{e.message.match(/\(erb\):(\d+)/)[1] rescue 'unknown'}"
puts "Error: #{e.message}"
# Show template with line numbers
template_string.lines.each_with_index do |line, index|
marker = (index + 1).to_s == e.message.match(/\(erb\):(\d+)/)[1] ? '>> ' : ' '
puts "#{marker}#{index + 1}: #{line}"
end
false
end
end
# Debug problematic template
template_with_error = <<~ERB
<% items.each do |item| %>
<%= item.name
<!-- Missing closing %> tag -->
<% end %>
ERB
ERBDebugger.compile_with_errors(template_with_error, 'items_template.erb')
Runtime errors occur during template execution when embedded Ruby code raises exceptions. These errors can be challenging to debug because the stack trace references compiled template code rather than original template source. Implement error handlers that capture template context and provide meaningful error messages.
class SafeTemplateRenderer
def initialize(template_string, name = 'anonymous')
@template_string = template_string
@template_name = name
@erb = ERB.new(template_string)
end
def render_with_error_handling(binding_context)
@erb.result(binding_context)
rescue => e
handle_template_error(e, binding_context)
end
private
def handle_template_error(error, binding_context)
error_context = extract_error_context(error)
puts "Template error in #{@template_name}:"
puts "Error: #{error.class}: #{error.message}"
puts "Context: #{error_context}"
# Log available variables for debugging
if binding_context.respond_to?(:local_variables)
vars = binding_context.local_variables.map { |v|
"#{v}: #{binding_context.local_variable_get(v).inspect}"
}.join(', ')
puts "Available variables: #{vars}"
end
# Return error message instead of crashing
"<div class='template-error'>Template rendering failed</div>"
end
def extract_error_context(error)
# Extract line number from backtrace
template_line = error.backtrace.find { |line| line.include?('(erb)') }
return 'unknown location' unless template_line
line_number = template_line.match(/:(\d+):/)[1].to_i
template_lines = @template_string.lines
start_line = [line_number - 2, 0].max
end_line = [line_number + 1, template_lines.length - 1].min
context_lines = (start_line..end_line).map do |i|
marker = i == line_number - 1 ? '>> ' : ' '
"#{marker}#{i + 1}: #{template_lines[i]}"
end
context_lines.join('')
end
end
Encoding errors arise when templates contain mixed character encodings or when template output conflicts with expected encoding. ERB inherits encoding behavior from the template string and binding context. Explicitly set template encoding to avoid conflicts.
class EncodingAwareRenderer
def self.render_with_encoding(template_string, variables, target_encoding = 'UTF-8')
# Ensure template string has correct encoding
template_string = template_string.force_encoding(target_encoding)
# Validate encoding
unless template_string.valid_encoding?
raise EncodingError, "Template contains invalid #{target_encoding} sequences"
end
erb = ERB.new(template_string)
result = erb.result_with_hash(variables)
# Ensure output encoding matches expectation
result.force_encoding(target_encoding)
rescue Encoding::InvalidByteSequenceError => e
puts "Encoding error: #{e.message}"
puts "Template encoding: #{template_string.encoding}"
raise
end
end
# Handle mixed encodings
template = "Name: <%= name %>"
variables = { name: "José".encode('ISO-8859-1') }
result = EncodingAwareRenderer.render_with_encoding(template, variables)
Variable scope debugging becomes complex when templates access undefined variables or when variable names conflict with ERB internals. Create debugging helpers that inspect available variables and detect common scoping issues.
module ERBDebugging
def debug_binding_variables(binding_ctx)
puts "Local variables:"
binding_ctx.local_variables.each do |var|
value = binding_ctx.local_variable_get(var)
puts " #{var}: #{value.inspect} (#{value.class})"
end
puts "Instance variables:"
binding_ctx.eval('instance_variables').each do |var|
value = binding_ctx.eval("#{var}")
puts " #{var}: #{value.inspect} (#{value.class})"
end
end
def template_with_debugging(template_string)
debug_template = <<~ERB
<%# Debug output %>
<% debug_binding_variables(binding) %>
<%# Original template %>
#{template_string}
ERB
ERB.new(debug_template)
end
end
Production Patterns
ERB templates in production environments require caching strategies, security considerations, and performance optimization. Template caching prevents repeated compilation overhead while maintaining template freshness. Implement multi-level caching with template modification detection and cache invalidation policies.
class ProductionTemplateCache
def initialize(cache_size: 100, ttl: 3600)
@cache = {}
@cache_times = {}
@file_mtimes = {}
@max_size = cache_size
@ttl = ttl
@mutex = Mutex.new
end
def get_template(template_path)
@mutex.synchronize do
cache_key = template_path
current_mtime = File.mtime(template_path)
# Check if template file changed
if @file_mtimes[cache_key] != current_mtime
invalidate_cache_entry(cache_key)
@file_mtimes[cache_key] = current_mtime
end
# Check cache expiration
if cache_expired?(cache_key)
invalidate_cache_entry(cache_key)
end
# Return cached template or create new one
@cache[cache_key] ||= create_compiled_template(template_path)
end
end
private
def create_compiled_template(template_path)
template_content = File.read(template_path)
erb = ERB.new(template_content, trim_mode: '-')
# Pre-compile template method for better performance
erb.def_method(CompiledTemplate, :render, template_path)
@cache_times[template_path] = Time.now
erb
end
def cache_expired?(cache_key)
cache_time = @cache_times[cache_key]
return true unless cache_time
Time.now - cache_time > @ttl
end
def invalidate_cache_entry(cache_key)
@cache.delete(cache_key)
@cache_times.delete(cache_key)
# Implement LRU eviction if cache too large
if @cache.size >= @max_size
oldest_key = @cache_times.min_by { |k, v| v }[0]
@cache.delete(oldest_key)
@cache_times.delete(oldest_key)
end
end
end
class CompiledTemplate
# Template methods get defined here by ERB.def_method
end
Rails integration patterns optimize ERB usage within web applications. Implement template inheritance, partial rendering, and layout systems that leverage Rails' rendering pipeline while maintaining clean separation between templates and application logic.
class ERBTemplateSystem
def initialize(template_root)
@template_root = template_root
@cache = ProductionTemplateCache.new
@layouts = {}
end
def render_with_layout(template_name, layout_name, variables = {})
template = load_template("#{template_name}.erb")
layout = load_layout("layouts/#{layout_name}.erb")
# Render template content first
content = template.result_with_hash(variables.merge(
render_partial: method(:render_partial)
))
# Render within layout
layout.result_with_hash(variables.merge(
content: content,
render_partial: method(:render_partial)
))
end
def render_partial(partial_name, locals = {})
partial_path = resolve_partial_path(partial_name)
template = load_template(partial_path)
template.result_with_hash(locals)
end
private
def load_template(template_path)
full_path = File.join(@template_root, template_path)
@cache.get_template(full_path)
end
def load_layout(layout_path)
@layouts[layout_path] ||= load_template(layout_path)
end
def resolve_partial_path(partial_name)
# Convert 'shared/header' to 'shared/_header.erb'
parts = partial_name.split('/')
parts[-1] = "_#{parts[-1]}"
"#{parts.join('/')}.erb"
end
end
# Usage in web application
template_system = ERBTemplateSystem.new('app/views')
html = template_system.render_with_layout('posts/show', 'application', {
post: current_post,
current_user: current_user
})
Security hardening prevents template injection and XSS vulnerabilities. Implement content sanitization, template source validation, and access controls that prevent malicious template execution while preserving legitimate functionality.
module ERBSecurity
ALLOWED_METHODS = %w[
strip downcase upcase capitalize
length size empty? present? blank?
to_s to_i to_f inspect
].freeze
DANGEROUS_PATTERNS = [
/system\s*\(/,
/`[^`]*`/,
/eval\s*\(/,
/exec\s*\(/,
/\$[A-Z_]+/, # Environment variables
/File\.(read|write|delete)/,
/Dir\./,
/require\s+/,
/load\s+/
].freeze
def self.validate_template_security(template_content)
DANGEROUS_PATTERNS.each do |pattern|
if template_content.match(pattern)
raise SecurityError, "Template contains potentially dangerous pattern: #{pattern.inspect}"
end
end
end
def self.create_secure_binding(variables)
# Create restricted binding context
secure_context = Object.new
# Add safe helper methods
secure_context.define_singleton_method(:h) { |text| html_escape(text) }
secure_context.define_singleton_method(:truncate) { |text, length| text[0...length] }
# Add variables as methods to prevent direct access
variables.each do |key, value|
secure_context.define_singleton_method(key) { sanitize_value(value) }
end
secure_context.send(:binding)
end
def self.sanitize_value(value)
case value
when String
html_escape(value)
when Array
value.map { |item| sanitize_value(item) }
when Hash
value.transform_values { |v| sanitize_value(v) }
else
value
end
end
def self.html_escape(text)
text.to_s.gsub(/[&<>"']/) do |char|
{
'&' => '&',
'<' => '<',
'>' => '>',
'"' => '"',
"'" => '''
}[char]
end
end
end
class SecureTemplateRenderer
def initialize(template_content)
ERBSecurity.validate_template_security(template_content)
@erb = ERB.new(template_content, safe_level: 4, trim_mode: '-')
end
def render(variables)
secure_binding = ERBSecurity.create_secure_binding(variables)
@erb.result(secure_binding)
end
end
Performance & Memory
ERB template performance depends on compilation overhead, binding creation costs, and memory allocation patterns. Template compilation represents the primary performance bottleneck, making template caching essential for production applications. Pre-compiled templates eliminate repeated parsing and compilation cycles.
require 'benchmark'
class ERBPerformanceTester
def self.benchmark_template_approaches(template_string, data_sets, iterations = 1000)
puts "Testing template performance with #{iterations} iterations"
# Test 1: Repeated compilation (worst case)
compile_time = Benchmark.measure do
iterations.times do |i|
erb = ERB.new(template_string)
erb.result_with_hash(data_sets[i % data_sets.length])
end
end
# Test 2: Single compilation, repeated execution
cached_time = Benchmark.measure do
erb = ERB.new(template_string)
iterations.times do |i|
erb.result_with_hash(data_sets[i % data_sets.length])
end
end
# Test 3: Pre-compiled method (fastest)
precompiled_time = Benchmark.measure do
erb = ERB.new(template_string)
erb.def_method(self, :render_template, 'benchmark_template')
iterations.times do |i|
data = data_sets[i % data_sets.length]
render_template(data)
end
end
puts "Compilation per render: #{compile_time.real.round(4)}s"
puts "Cached compilation: #{cached_time.real.round(4)}s"
puts "Pre-compiled method: #{precompiled_time.real.round(4)}s"
puts "Speedup (cached vs compiled): #{(compile_time.real / cached_time.real).round(2)}x"
puts "Speedup (precompiled vs compiled): #{(compile_time.real / precompiled_time.real).round(2)}x"
end
end
# Performance test with realistic data
template = <<~ERB
<html>
<h1><%= title %></h1>
<ul>
<% items.each do |item| %>
<li><%= item[:name] %> - $<%= item[:price] %></li>
<% end %>
</ul>
</html>
ERB
test_data = Array.new(10) do |i|
{
title: "Report #{i}",
items: Array.new(50) { |j|
{ name: "Item #{j}", price: rand(10.0..100.0).round(2) }
}
}
end
ERBPerformanceTester.benchmark_template_approaches(template, test_data)
Memory optimization requires careful attention to string allocation and garbage collection patterns. ERB generates significant string objects during template rendering. Use string builders, optimize string interpolation, and implement object pooling for frequently rendered templates.
class MemoryOptimizedRenderer
def initialize
@string_pool = []
@erb_cache = {}
end
def render_efficiently(template_path, variables)
# Reuse compiled templates
erb = @erb_cache[template_path] ||= compile_template(template_path)
# Use pooled string buffer
buffer = acquire_string_buffer
begin
# Capture template output into buffer
result = erb.result(create_binding(variables, buffer))
buffer.string.dup # Return copy, not the buffer itself
ensure
release_string_buffer(buffer)
end
end
private
def compile_template(template_path)
template_content = File.read(template_path)
ERB.new(template_content, trim_mode: '-').tap do |erb|
# Enable explicit output buffer for memory control
erb.def_method(self, :render_with_buffer, template_path)
end
end
def create_binding(variables, buffer)
# Create binding with output buffer
context = Object.new
context.define_singleton_method(:_erbout) { buffer }
variables.each do |key, value|
context.define_singleton_method(key) { value }
end
context.send(:binding)
end
def acquire_string_buffer
@string_pool.pop || StringIO.new
end
def release_string_buffer(buffer)
buffer.rewind
buffer.truncate(0)
@string_pool.push(buffer) if @string_pool.length < 10
end
end
Large template optimization involves partitioning templates, streaming output, and lazy evaluation techniques. Break large templates into smaller components to reduce memory footprint and enable parallel processing.
class StreamingTemplateRenderer
def initialize(template_path)
@template_sections = partition_template(File.read(template_path))
end
def render_streaming(variables, output_stream = STDOUT)
@template_sections.each do |section_erb|
chunk = section_erb.result_with_hash(variables)
output_stream.write(chunk)
output_stream.flush
# Allow garbage collection between sections
GC.start if @gc_between_sections
end
end
def render_parallel(variables, thread_count = 4)
# Split template sections among threads
section_groups = @template_sections.each_slice(
(@template_sections.length / thread_count.to_f).ceil
).to_a
results = Array.new(section_groups.length)
threads = []
section_groups.each_with_index do |sections, index|
threads << Thread.new do
group_output = StringIO.new
sections.each do |section_erb|
chunk = section_erb.result_with_hash(variables)
group_output.write(chunk)
end
results[index] = group_output.string
end
end
threads.each(&:join)
results.join('')
end
private
def partition_template(template_content)
# Split template at logical boundaries (e.g., major sections)
sections = template_content.split(/<!-- SECTION_BREAK -->/)
sections.map { |section| ERB.new(section.strip) }
end
end
Profile-guided optimization identifies performance bottlenecks in template rendering. Use Ruby profiling tools to analyze method calls, memory allocation, and execution patterns within template code.
require 'memory_profiler'
require 'ruby-prof'
class ERBProfiler
def self.profile_memory_usage(template_path, variables)
report = MemoryProfiler.report do
erb = ERB.new(File.read(template_path))
1000.times { erb.result_with_hash(variables) }
end
puts "Memory Report for #{template_path}:"
puts "Total allocated: #{report.total_allocated_memsize} bytes"
puts "Total retained: #{report.total_retained_memsize} bytes"
puts "\nTop allocations by class:"
report.allocated_memory_by_class.first(10).each do |class_name, size|
puts " #{class_name}: #{size} bytes"
end
end
def self.profile_cpu_usage(template_path, variables)
RubyProf.start
erb = ERB.new(File.read(template_path))
1000.times { erb.result_with_hash(variables) }
result = RubyProf.stop
puts "CPU Profile for #{template_path}:"
printer = RubyProf::FlatPrinter.new(result)
printer.print(STDOUT, min_percent: 2)
end
end
Common Pitfalls
Variable scope confusion represents the most frequent ERB template issue. ERB templates execute within the binding context passed to the result
method, but developers often misunderstand which variables and methods are accessible. Local variables defined within template logic blocks remain available throughout the template, but instance variables require explicit definition in the binding context.
# Common scope pitfall
class TemplateExample
def initialize
@instance_var = "Available in template"
end
def render_template
local_var = "Not available in template"
template = <<~ERB
Instance variable: <%= @instance_var %>
Local variable: <%= local_var %> # NameError: undefined local variable
Template local: <%= template_local if defined?(template_local) %>
<% template_local = "Defined in template" %>
Now available: <%= template_local %>
ERB
ERB.new(template).result(binding)
end
end
# Correct scope handling
class CorrectTemplateExample
def render_with_locals
variables = {
instance_var: @instance_var,
local_var: "Now accessible",
helper_method: method(:format_text)
}
template = <<~ERB
Instance: <%= instance_var %>
Local: <%= local_var %>
Helper: <%= helper_method.call("test") %>
ERB
ERB.new(template).result_with_hash(variables)
end
private
def format_text(text)
text.upcase
end
end
HTML escaping negligence creates XSS vulnerabilities when templates render user-supplied content. ERB does not automatically escape output, requiring manual escaping of all dynamic content that might contain HTML characters. Implement consistent escaping strategies and create helper methods for common escaping scenarios.
# Dangerous: unescaped user content
dangerous_template = <<~ERB
<p>User comment: <%= user_comment %></p>
<p>User name: <%= user.name %></p>
ERB
# If user_comment contains: "<script>alert('XSS')</script>"
# Template output includes executable JavaScript
# Safe: properly escaped content
module EscapeHelpers
def html_escape(text)
return '' if text.nil?
text.to_s.gsub(/[&<>"']/) do |char|
case char
when '&' then '&'
when '<' then '<'
when '>' then '>'
when '"' then '"'
when "'" then '''
end
end
end
# Alias for brevity
alias_method :h, :html_escape
def safe_render(template_string, variables)
# Auto-escape all string variables
escaped_variables = variables.transform_values do |value|
case value
when String then html_escape(value)
when Array then value.map { |item| item.is_a?(String) ? html_escape(item) : item }
else value
end
end
ERB.new(template_string).result_with_hash(escaped_variables.merge(h: method(:html_escape)))
end
end
safe_template = <<~ERB
<p>User comment: <%= h(user_comment) %></p>
<p>User name: <%= h(user.name) %></p>
ERB
Encoding mismatches cause character corruption when templates process international content. ERB inherits encoding from template strings and binding contexts, but mixed encodings within a single template can produce invalid output. Establish consistent encoding policies and validate encoding compatibility before template execution.
# Encoding pitfall example
class EncodingPitfall
def demonstrate_encoding_issues
# Template in UTF-8
template = "Name: <%= name %>, City: <%= city %>"
# Mixed encoding variables
variables = {
name: "José".force_encoding('UTF-8'),
city: "São Paulo".force_encoding('ISO-8859-1') # Different encoding!
}
begin
ERB.new(template).result_with_hash(variables)
rescue Encoding::CompatibilityError => e
puts "Encoding error: #{e.message}"
return fix_encoding_issues(template, variables)
end
end
private
def fix_encoding_issues(template, variables)
# Normalize all variables to UTF-8
normalized_variables = variables.transform_values do |value|
if value.respond_to?(:encoding)
value.encode('UTF-8', invalid: :replace, undef: :replace)
else
value
end
end
# Ensure template is UTF-8
template = template.force_encoding('UTF-8')
ERB.new(template).result_with_hash(normalized_variables)
end
end
# Correct encoding handling
class SafeEncodingHandler
def render_with_encoding_safety(template_string, variables, target_encoding = 'UTF-8')
# Validate and convert template encoding
safe_template = ensure_encoding(template_string, target_encoding)
# Normalize variable encodings
safe_variables = normalize_variable_encodings(variables, target_encoding)
result = ERB.new(safe_template).result_with_hash(safe_variables)
result.force_encoding(target_encoding)
end
private
def ensure_encoding(text, target_encoding)
return text if text.encoding.name == target_encoding && text.valid_encoding?
text.encode(target_encoding, invalid: :replace, undef: :replace, replace: '?')
rescue Encoding::InvalidByteSequenceError
text.force_encoding('BINARY').encode(target_encoding, invalid: :replace, undef: :replace)
end
def normalize_variable_encodings(variables, target_encoding)
variables.transform_values do |value|
case value
when String then ensure_encoding(value, target_encoding)
when Array then value.map { |item| item.is_a?(String) ? ensure_encoding(item, target_encoding) : item }
when Hash then normalize_variable_encodings(value, target_encoding)
else value
end
end
end
end
Performance degradation from repeated template compilation represents a subtle but significant pitfall. Developers often create new ERB objects for each render operation, incurring compilation overhead that scales linearly with request volume. Template compilation costs can exceed actual rendering time for simple templates.
# Performance pitfall: repeated compilation
class SlowTemplateRenderer
def render_user_profile(user)
# Creates new ERB object every time - SLOW!
template_string = File.read('user_profile_template.erb')
erb = ERB.new(template_string)
erb.result_with_hash(user: user)
end
end
# Optimized: template caching
class FastTemplateRenderer
def initialize
@compiled_templates = {}
@file_mtimes = {}
end
def render_user_profile(user)
template_path = 'user_profile_template.erb'
erb = get_compiled_template(template_path)
erb.result_with_hash(user: user)
end
private
def get_compiled_template(template_path)
current_mtime = File.mtime(template_path)
# Check if template file changed
if @file_mtimes[template_path] != current_mtime
@compiled_templates[template_path] = nil
@file_mtimes[template_path] = current_mtime
end
# Return cached or create new compiled template
@compiled_templates[template_path] ||= begin
template_string = File.read(template_path)
ERB.new(template_string, trim_mode: '-')
end
end
end
Template logic complexity violations occur when templates contain excessive business logic, making them difficult to test and maintain. ERB templates should focus on presentation logic while delegating complex calculations and data processing to helper methods or model objects.
# Anti-pattern: complex logic in template
complex_template = <<~ERB
<%
# Complex calculation in template - BAD!
total_price = 0
items.each do |item|
discount = case item.category
when 'electronics' then 0.10
when 'books' then 0.05
else 0
end
discounted_price = item.price * (1 - discount)
total_price += discounted_price
end
# Tax calculation - also bad!
tax_rate = customer.state == 'CA' ? 0.0875 : 0.06
final_total = total_price * (1 + tax_rate)
%>
Total: $<%= sprintf('%.2f', final_total) %>
ERB
# Better: logic in helper methods
class OrderPresenter
def initialize(order, customer)
@order = order
@customer = customer
end
def total_with_tax
subtotal = calculate_discounted_subtotal
tax_amount = calculate_tax(subtotal)
subtotal + tax_amount
end
def formatted_total
sprintf('$%.2f', total_with_tax)
end
private
def calculate_discounted_subtotal
@order.items.sum do |item|
discount_rate = discount_for_category(item.category)
item.price * (1 - discount_rate)
end
end
def discount_for_category(category)
case category
when 'electronics' then 0.10
when 'books' then 0.05
else 0
end
end
def calculate_tax(subtotal)
tax_rate = @customer.state == 'CA' ? 0.0875 : 0.06
subtotal * tax_rate
end
end
# Clean template using presenter
clean_template = <<~ERB
<div class="order-total">
Total: <%= order_presenter.formatted_total %>
</div>
ERB
Reference
ERB Class Methods
Method | Parameters | Returns | Description |
---|---|---|---|
ERB.new(template, safe_level: nil, trim_mode: nil, eoutvar: '_erbout') |
template (String), options |
ERB |
Creates new ERB template object with optional safety and trim settings |
ERB.version |
None | String |
Returns ERB version string |
ERB Instance Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#result(binding = TOPLEVEL_BINDING) |
binding (Binding) |
String |
Executes template within specified binding context |
#result_with_hash(variables = {}) |
variables (Hash) |
String |
Executes template with hash variables as local variables |
#run(binding = TOPLEVEL_BINDING) |
binding (Binding) |
nil |
Executes template and outputs result directly |
#def_method(mod, methodname, fname='(ERB)') |
mod (Module), methodname (Symbol), fname (String) |
Method | Defines template as method on specified module |
#def_class(superklass=Object, methodname="erb") |
superklass (Class), methodname (String) |
Class |
Creates class with template method |
#set_eoutvar(compiler, eoutvar = '_erbout') |
compiler , eoutvar (String) |
String |
Sets output buffer variable name |
Template Syntax Elements
Syntax | Purpose | Output | Example |
---|---|---|---|
<%= expression %> |
Output expression result | Yes | <%= user.name %> |
<% statement %> |
Execute Ruby code | No | <% users.each do |u| %> |
<%# comment %> |
Template comment | No | <%# This is a comment %> |
<%- expression -%> |
Trim whitespace | Conditional | <%- user.name -%> |
Trim Mode Options
Mode | Description | Trailing Newline | Leading Whitespace |
---|---|---|---|
nil |
No trimming | Preserved | Preserved |
'-' |
Trim lines ending with -%> |
Removed | Preserved |
'<>' |
Trim whitespace around tags | Removed | Removed |
'<>-' |
Combination of <> and - |
Removed | Removed |
Safe Level Restrictions
Level | Restrictions | Use Case |
---|---|---|
nil |
No restrictions | Trusted templates |
1 |
Prohibits dangerous operations | Limited trust |
2 |
Restricts file and process operations | Sandboxed execution |
3 |
Prevents most system interactions | User-generated templates |
4 |
Maximum restrictions | Untrusted content |
Common Binding Methods
Method | Parameters | Returns | Description |
---|---|---|---|
binding.local_variable_get(name) |
name (Symbol) |
Object |
Retrieves local variable value |
binding.local_variable_set(name, value) |
name (Symbol), value |
Object |
Sets local variable |
binding.local_variables |
None | Array<Symbol> |
Lists available local variables |
binding.eval(string) |
string (String) |
Object |
Evaluates Ruby code in context |
Error Classes
Exception | Cause | Typical Scenario |
---|---|---|
SyntaxError |
Invalid Ruby syntax in template | Malformed ERB tags, missing end statements |
NameError |
Undefined variable or method | Variable not in binding scope |
SecurityError |
Safe mode violation | Restricted operation in safe template |
Encoding::CompatibilityError |
Mixed character encodings | International content with different encodings |
NoMethodError |
Method called on nil/wrong type | Template assumes object structure |
Performance Characteristics
Operation | Relative Cost | Optimization Strategy |
---|---|---|
Template compilation | High (10-100x render) | Cache compiled templates |
Binding creation | Medium (2-5x render) | Reuse binding objects |
Template execution | Low (baseline) | Pre-compile methods |
String allocation | Medium (varies) | Use string pools |
File I/O | High (disk dependent) | Template caching |
Best Practices Summary
Practice | Rationale | Implementation |
---|---|---|
Cache compiled templates | Eliminates repeated compilation cost | Template cache with file modification detection |
Escape all user content | Prevents XSS vulnerabilities | HTML escape helpers, auto-escaping |
Use consistent encoding | Avoids character corruption | Normalize to UTF-8, validate encodings |
Keep templates simple | Improves maintainability | Move logic to helpers, use presenters |
Handle errors gracefully | Better user experience | Error boundaries, fallback content |
Profile performance | Identifies bottlenecks | Memory and CPU profiling tools |