Rails ERB模板 #

一、ERB概述 #

1.1 什么是ERB #

ERB(Embedded Ruby)是Rails默认的模板引擎,允许在HTML中嵌入Ruby代码。

1.2 ERB标签 #

标签 说明 示例
<% %> 执行Ruby代码,不输出 <% @article.save %>
<%= %> 执行并输出结果 <%= @article.title %>
<% -%> 执行代码,去除后面换行 <% if true -%>
<%# %> 注释 <%# 这是一个注释 %>

二、输出内容 #

2.1 基本输出 #

erb
<!-- 输出变量 -->
<h1><%= @article.title %></h1>
<p><%= @article.body %></p>

<!-- 输出表达式 -->
<p>文章数量:<%= Article.count %></p>
<p>当前时间:<%= Time.now.strftime('%Y-%m-%d') %></p>

<!-- 输出链接 -->
<%= link_to '首页', root_path %>
<%= link_to '文章详情', article_path(@article) %>

2.2 HTML转义 #

erb
<!-- 自动转义HTML(默认) -->
<%= '<script>alert("XSS")</script>' %>
<!-- 输出: &lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt; -->

<!-- 原样输出HTML -->
<%= raw '<strong>粗体文字</strong>' %>

<!-- 或使用html_safe -->
<%= '<strong>粗体文字</strong>'.html_safe %>

<!-- 安全连接 -->
<%= safe_join(['项目1', '项目2'], ', ') %>

2.3 sanitize #

erb
<!-- 过滤危险标签,保留安全标签 -->
<%= sanitize @article.body %>

<!-- 允许特定标签和属性 -->
<%= sanitize @article.body, tags: %w[p strong em a], attributes: %w[href] %>

三、控制结构 #

3.1 条件判断 #

erb
<!-- if语句 -->
<% if @article.published? %>
  <span class="badge">已发布</span>
<% elsif @article.draft? %>
  <span class="badge">草稿</span>
<% else %>
  <span class="badge">未知状态</span>
<% end %>

<!-- unless语句 -->
<% unless @article.comments.empty? %>
  <p>评论数:<%= @article.comments.count %></p>
<% end %>

<!-- 三元运算符 -->
<p>状态:<%= @article.published? ? '已发布' : '未发布' %></p>

3.2 循环 #

erb
<!-- each循环 -->
<% @articles.each do |article| %>
  <div class="article">
    <h2><%= article.title %></h2>
    <p><%= truncate(article.body, length: 100) %></p>
  </div>
<% end %>

<!-- each_with_index -->
<% @articles.each_with_index do |article, index| %>
  <div class="article article-<%= index %>">
    <h2><%= index + 1 %>. <%= article.title %></h2>
  </div>
<% end %>

<!-- for循环(不推荐) -->
<% for article in @articles %>
  <div class="article">
    <h2><%= article.title %></h2>
  </div>
<% end %>

<!-- times循环 -->
<% 5.times do |i| %>
  <p>第 <%= i + 1 %> 次</p>
<% end %>

3.3 case语句 #

erb
<% case @article.status %>
<% when 'published' %>
  <span class="badge bg-success">已发布</span>
<% when 'draft' %>
  <span class="badge bg-secondary">草稿</span>
<% when 'archived' %>
  <span class="badge bg-dark">已归档</span>
<% else %>
  <span class="badge bg-warning">未知</span>
<% end %>

四、部分视图 #

4.1 渲染部分视图 #

erb
<!-- 渲染部分视图 -->
<%= render 'shared/header' %>
<!-- 渲染 app/views/shared/_header.html.erb -->

<!-- 渲染集合 -->
<%= render @articles %>
<!-- 渲染 app/views/articles/_article.html.erb,自动传递article变量 -->

<!-- 渲染集合带局部变量 -->
<%= render partial: 'article', collection: @articles, as: :item %>

<!-- 渲染单个对象 -->
<%= render @article %>
<!-- 渲染 app/views/articles/_article.html.erb -->

4.2 部分视图文件 #

erb
<!-- app/views/articles/_article.html.erb -->
<div class="article">
  <h2><%= article.title %></h2>
  <p><%= truncate(article.body, length: 100) %></p>
  <div class="meta">
    <span>作者:<%= article.author.name %></span>
    <span>时间:<%= article.created_at.strftime('%Y-%m-%d') %></span>
  </div>
</div>

4.3 传递局部变量 #

erb
<!-- 传递局部变量 -->
<%= render 'shared/card', title: '文章统计', count: @articles.count %>

<!-- app/views/shared/_card.html.erb -->
<div class="card">
  <div class="card-header">
    <%= title %>
  </div>
  <div class="card-body">
    <p>数量:<%= count %></p>
  </div>
</div>

4.4 集合渲染优化 #

erb
<!-- 使用cached选项缓存 -->
<%= render partial: 'article', collection: @articles, cached: true %>

<!-- 自定义间距 -->
<%= render partial: 'article', collection: @articles, spacer_template: 'divider' %>

<!-- app/views/articles/_divider.html.erb -->
<hr class="divider">

五、布局 #

5.1 布局文件 #

erb
<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
<head>
  <title><%= page_title(yield(:title)) %></title>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <%= csrf_meta_tags %>
  <%= csp_meta_tag %>
  
  <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
  <%= javascript_importmap_tags %>
</head>
<body>
  <%= render 'shared/header' %>
  
  <main>
    <%= yield %>  <!-- 内容插入点 -->
  </main>
  
  <%= render 'shared/footer' %>
</body>
</html>

5.2 多个yield #

erb
<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
<head>
  <title><%= yield(:title) %></title>
  <%= yield(:meta_tags) %>
</head>
<body>
  <header>
    <%= yield(:header) %>
  </header>
  
  <main>
    <%= yield %>  <!-- 默认内容 -->
  </main>
  
  <footer>
    <%= yield(:footer) %>
  </footer>
</body>
</html>

<!-- app/views/articles/show.html.erb -->
<% content_for :title, @article.title %>

<% content_for :meta_tags do %>
  <meta name="description" content="<%= @article.summary %>">
<% end %>

<% content_for :header do %>
  <h1><%= @article.title %></h1>
<% end %>

<article>
  <%= @article.body %>
</article>

5.3 布局继承 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  layout 'application'  # 默认布局
  
  # 条件布局
  layout :choose_layout
  
  private
  
  def choose_layout
    if request.xhr?
      false  # 不使用布局
    else
      'application'
    end
  end
end

# 特定动作不使用布局
class ArticlesController < ApplicationController
  layout 'application', except: [:rss, :atom]
end

六、辅助方法 #

6.1 链接辅助方法 #

erb
<!-- 基本链接 -->
<%= link_to '首页', root_path %>

<!-- 带HTML属性的链接 -->
<%= link_to '文章', articles_path, class: 'btn btn-primary', id: 'articles-link' %>

<!-- 带确认对话框 -->
<%= link_to '删除', article_path(@article), 
    data: { turbo_method: :delete, turbo_confirm: '确定删除?' } %>

<!-- 块链接 -->
<%= link_to article_path(@article) do %>
  <strong><%= @article.title %></strong>
  <span>点击查看</span>
<% end %>

<!-- 邮件链接 -->
<%= mail_to 'info@example.com', '联系我们' %>

<!-- 电话链接 -->
<%= phone_to '1234567890', '拨打电话' %>

6.2 表单辅助方法 #

erb
<!-- 基本表单 -->
<%= form_with model: @article do |form| %>
  <div class="field">
    <%= form.label :title %>
    <%= form.text_field :title, class: 'form-control' %>
  </div>
  
  <div class="field">
    <%= form.label :body %>
    <%= form.text_area :body, rows: 10, class: 'form-control' %>
  </div>
  
  <div class="actions">
    <%= form.submit '保存', class: 'btn btn-primary' %>
  </div>
<% end %>

<!-- 表单选项 -->
<%= form.select :category_id, Category.pluck(:name, :id) %>
<%= form.select :status, [['草稿', 'draft'], ['发布', 'published']] %>
<%= form.collection_select :author_id, Author.all, :id, :name %>

6.3 资源辅助方法 #

erb
<!-- 样式表 -->
<%= stylesheet_link_tag 'application' %>
<%= stylesheet_link_tag 'print', media: 'print' %>

<!-- JavaScript -->
<%= javascript_importmap_tags %>
<%= javascript_include_tag 'application' %>

<!-- 图片 -->
<%= image_tag 'logo.png', alt: 'Logo' %>
<%= image_tag 'icon.png', size: '16x16' %>
<%= image_tag 'photo.jpg', class: 'img-fluid' %>

<!-- 视频和音频 -->
<%= video_tag 'intro.mp4', controls: true %>
<%= audio_tag 'music.mp3', controls: true %>

6.4 文本辅助方法 #

erb
<!-- 截断文本 -->
<%= truncate(@article.body, length: 100) %>
<%= truncate(@article.body, length: 100, omission: '...') %>

<!-- 高亮文本 -->
<%= highlight(@article.body, 'Rails') %>

<!-- 简单格式化 -->
<%= simple_format(@article.body) %>

<!-- 自动链接 -->
<%= auto_link(@article.body) %>

<!-- 复数化 -->
<%= pluralize(@articles.count, 'article') %>

6.5 数字辅助方法 #

erb
<!-- 格式化数字 -->
<%= number_with_delimiter(1234567) %>  <!-- 1,234,567 -->
<%= number_with_precision(3.14159, precision: 2) %>  <!-- 3.14 -->

<!-- 货币格式 -->
<%= number_to_currency(123.45) %>  <!-- $123.45 -->
<%= number_to_currency(123.45, unit: '¥') %>  <!-- ¥123.45 -->

<!-- 百分比格式 -->
<%= number_to_percentage(0.5, precision: 0) %>  <!-- 50% -->

<!-- 文件大小 -->
<%= number_to_human_size(1234567) %>  <!-- 1.18 MB -->

6.6 日期辅助方法 #

erb
<!-- 格式化日期 -->
<%= l Date.today, format: :long %>  <!-- 2024年3月28日 -->
<%= l Time.now, format: :short %>  <!-- 28日 15:30 -->

<!-- 距离时间 -->
<%= distance_of_time_in_words(Time.now, Time.now + 1.day) %>  <!-- about 1 day -->

<!-- 时间标签 -->
<%= time_tag Date.today %>  <!-- <time datetime="2024-03-28">2024-03-28</time> -->

七、调试辅助方法 #

7.1 debug #

erb
<!-- 格式化输出对象 -->
<%= debug(@article) %>

<!-- 输出YAML格式 -->
<%= simple_format(@article.to_yaml) %>

7.2 console #

erb
<!-- 在页面中打开控制台 -->
<%= console %>

八、最佳实践 #

8.1 视图保持简洁 #

erb
<!-- 不推荐:视图中包含复杂逻辑 -->
<% if @article.status == 'published' && @article.published_at <= Time.now && current_user.admin? %>
  <span class="badge">管理员可见</span>
<% end %>

<!-- 推荐:使用辅助方法 -->
<%= admin_badge(@article) if show_admin_badge?(@article) %>

<!-- app/helpers/articles_helper.rb -->
module ArticlesHelper
  def admin_badge(article)
    content_tag :span, '管理员可见', class: 'badge'
  end
  
  def show_admin_badge?(article)
    article.published? && current_user&.admin?
  end
end

8.2 使用部分视图 #

erb
<!-- 不推荐:重复代码 -->
<% @articles.each do |article| %>
  <div class="article">
    <h2><%= article.title %></h2>
    <p><%= truncate(article.body, length: 100) %></p>
    <div class="meta">
      <span>作者:<%= article.author.name %></span>
    </div>
  </div>
<% end %>

<!-- 推荐:使用部分视图 -->
<%= render @articles %>

8.3 避免N+1查询 #

erb
<!-- 不推荐:N+1查询 -->
<% @articles.each do |article| %>
  <p><%= article.author.name %></p>  <!-- 每次循环查询作者 -->
<% end %>

<!-- 推荐:预加载关联 -->
<!-- 控制器中 -->
@articles = Article.includes(:author).all

<!-- 视图中 -->
<% @articles.each do |article| %>
  <p><%= article.author.name %></p>  <!-- 不会额外查询 -->
<% end %>

九、总结 #

9.1 核心要点 #

要点 说明
ERB标签 <% %>执行,<%= %>输出
部分视图 复用视图代码
布局 页面框架结构
辅助方法 简化视图代码
HTML转义 防止XSS攻击

9.2 下一步 #

现在你已经掌握了ERB模板,接下来让我们学习 布局与渲染,深入了解Rails的布局系统!

最后更新:2026-03-28