CrackedRuby logo

CrackedRuby

ERB Templates

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 '&amp;'
      when '<' then '&lt;'
      when '>' then '&gt;'
      when '"' then '&quot;'
      when "'" then '&#39;'
      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|
      {
        '&' => '&amp;',
        '<' => '&lt;',
        '>' => '&gt;',
        '"' => '&quot;',
        "'" => '&#39;'
      }[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 '&amp;'
      when '<' then '&lt;'
      when '>' then '&gt;'
      when '"' then '&quot;'
      when "'" then '&#39;'
      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