Rails布局与渲染 #

一、布局概述 #

1.1 什么是布局 #

布局是包裹视图内容的模板框架,定义了页面的公共结构(头部、导航、页脚等)。

1.2 布局查找顺序 #

Rails按以下顺序查找布局:

  1. app/views/layouts/[控制器名].html.erb
  2. app/views/layouts/application.html.erb

二、布局基础 #

2.1 默认布局 #

erb
<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
<head>
  <title>MyApp</title>
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <%= csrf_meta_tags %>
  <%= csp_meta_tag %>
  <%= stylesheet_link_tag "application" %>
  <%= javascript_importmap_tags %>
</head>
<body>
  <%= yield %>
</body>
</html>

2.2 控制器特定布局 #

erb
<!-- app/views/layouts/articles.html.erb -->
<!DOCTYPE html>
<html>
<head>
  <title>文章 - MyApp</title>
  <%= csrf_meta_tags %>
  <%= stylesheet_link_tag "application" %>
</head>
<body>
  <nav>
    <%= link_to '文章列表', articles_path %>
  </nav>
  
  <%= yield %>
</body>
</html>

2.3 指定布局 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  layout 'admin'  # 使用 admin.html.erb 布局
  
  # 条件布局
  layout :determine_layout
  
  private
  
  def determine_layout
    if current_user&.admin?
      'admin'
    else
      'application'
    end
  end
end

2.4 禁用布局 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  layout false  # 禁用所有布局
  
  # 特定动作禁用布局
  layout false, only: [:rss, :atom]
end

三、yield和content_for #

3.1 基本yield #

erb
<!-- app/views/layouts/application.html.erb -->
<body>
  <header>
    <%= render 'shared/header' %>
  </header>
  
  <main>
    <%= yield %>  <!-- 视图内容插入点 -->
  </main>
  
  <footer>
    <%= render 'shared/footer' %>
  </footer>
</body>

3.2 命名yield #

erb
<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
<head>
  <title><%= yield(:title) || 'MyApp' %></title>
  <%= yield(:meta_tags) %>
  <%= yield(:stylesheets) %>
</head>
<body>
  <header>
    <%= yield(:header) %>
    <%= render 'shared/nav' %>
  </header>
  
  <main>
    <%= yield %>  <!-- 默认内容 -->
  </main>
  
  <aside>
    <%= yield(:sidebar) %>
  </aside>
  
  <footer>
    <%= yield(:footer) %>
    <%= render 'shared/footer' %>
  </footer>
  
  <%= yield(:javascripts) %>
</body>
</html>

3.3 content_for #

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

<% content_for :meta_tags do %>
  <meta name="description" content="<%= @article.summary %>">
  <meta name="keywords" content="<%= @article.tags.join(', ') %>">
<% end %>

<% content_for :stylesheets do %>
  <%= stylesheet_link_tag 'articles' %>
<% end %>

<% content_for :sidebar do %>
  <div class="sidebar">
    <h3>相关文章</h3>
    <%= render 'related_articles' %>
  </div>
<% end %>

<article>
  <h1><%= @article.title %></h1>
  <div class="content">
    <%= @article.body %>
  </div>
</article>

<% content_for :javascripts do %>
  <%= javascript_include_tag 'articles' %>
<% end %>

3.4 content_for?检查 #

erb
<!-- app/views/layouts/application.html.erb -->
<head>
  <title><%= yield(:title) || 'MyApp' %></title>
  
  <% if content_for?(:meta_tags) %>
    <%= yield(:meta_tags) %>
  <% else %>
    <meta name="description" content="默认描述">
  <% end %>
</head>

四、渲染选项 #

4.1 渲染动作 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def show
    @article = Article.find(params[:id])
    
    # 渲染当前控制器的show视图
    render :show
    
    # 渲染同一控制器的其他动作视图
    render :index
    
    # 渲染其他控制器的视图
    render 'users/profile'
    
    # 渲染绝对路径
    render file: Rails.root.join('public', '404.html')
  end
end

4.2 渲染选项 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def show
    @article = Article.find(params[:id])
    
    # 渲染指定模板
    render template: 'articles/special'
    
    # 渲染内联模板
    render inline: '<% @articles.each do |a| %><p><%= a.title %></p><% end %>'
    
    # 渲染纯文本
    render plain: 'Hello World'
    
    # 渲染HTML
    render html: '<h1>Hello</h1>'.html_safe
    
    # 渲染JSON
    render json: @article
    
    # 渲染XML
    render xml: @article
    
    # 渲染JavaScript
    render js: "alert('Hello')"
    
    # 渲染原始内容
    render body: 'raw body content'
  end
end

4.3 渲染状态 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def create
    @article = Article.new(article_params)
    
    if @article.save
      redirect_to @article, notice: '创建成功'
    else
      render :new, status: :unprocessable_entity
    end
  end
end

4.4 渲染布局选项 #

ruby
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def show
    @article = Article.find(params[:id])
    
    # 使用特定布局
    render :show, layout: 'print'
    
    # 不使用布局
    render :show, layout: false
    
    # 使用布局并传递局部变量
    render :show, layout: 'application', locals: { show_sidebar: true }
  end
end

五、布局继承 #

5.1 基本继承 #

ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  layout 'application'
end

# app/controllers/admin/application_controller.rb
module Admin
  class ApplicationController < ::ApplicationController
    layout 'admin'
  end
end

# app/controllers/admin/users_controller.rb
module Admin
  class UsersController < Admin::ApplicationController
    # 使用 admin 布局
  end
end

5.2 布局层级 #

text
app/views/layouts/
├── application.html.erb      # 基础布局
├── admin.html.erb            # 管理后台布局
├── admin/
│   └── users.html.erb        # 管理后台用户布局
└── devise.html.erb           # Devise布局

六、模板查找 #

6.1 查找路径 #

Rails在以下路径查找模板:

text
app/views/
├── articles/
│   ├── index.html.erb
│   ├── show.html.erb
│   ├── _article.html.erb
│   └── _form.html.erb
├── shared/
│   ├── _header.html.erb
│   └── _footer.html.erb
└── layouts/
    ├── application.html.erb
    └── admin.html.erb

6.2 自定义查找路径 #

ruby
# config/application.rb
module MyApp
  class Application < Rails::Application
    # 添加视图查找路径
    config.paths['app/views'].unshift Rails.root.join('app', 'themes', 'default', 'views')
  end
end

七、渲染集合 #

7.1 渲染集合 #

erb
<!-- 渲染集合 -->
<%= render @articles %>

<!-- 等价于 -->
<%= render partial: 'article', collection: @articles %>

<!-- 自定义局部变量名 -->
<%= render partial: 'article', collection: @articles, as: :item %>

7.2 集合渲染优化 #

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

<!-- 添加间距模板 -->
<%= render partial: 'article', collection: @articles, spacer_template: 'divider' %>

<!-- 空集合处理 -->
<%= render partial: 'article', collection: @articles || '暂无文章' %>

7.3 部分视图 #

erb
<!-- app/views/articles/_article.html.erb -->
<div class="article" id="article-<%= article.id %>">
  <h2><%= link_to article.title, article %></h2>
  <p><%= truncate(article.body, length: 100) %></p>
  <div class="meta">
    <span><%= article.author.name %></span>
    <span><%= l article.created_at, format: :short %></span>
  </div>
</div>

八、高级布局技巧 #

8.1 嵌套布局 #

erb
<!-- app/views/layouts/admin.html.erb -->
<% content_for :stylesheets do %>
  <%= stylesheet_link_tag 'admin' %>
<% end %>

<% content_for :sidebar do %>
  <nav class="admin-nav">
    <%= render 'admin/shared/nav' %>
  </nav>
<% end %>

<%= render template: 'layouts/application' %>

8.2 响应式布局 #

ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  layout :layout_by_resource
  
  private
  
  def layout_by_resource
    if devise_controller? && resource_name == :user && action_name == 'login'
      'devise'
    else
      'application'
    end
  end
end

8.3 动态布局 #

ruby
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  layout :choose_layout
  
  private
  
  def choose_layout
    case
    when request.xhr?
      false
    when request.format.json?
      false
    when current_user&.admin?
      'admin'
    when mobile_device?
      'mobile'
    else
      'application'
    end
  end
  
  def mobile_device?
    request.user_agent =~ /Mobile|webOS/
  end
end

九、最佳实践 #

9.1 布局组织 #

text
app/views/layouts/
├── application.html.erb      # 主布局
├── admin.html.erb            # 管理后台布局
├── devise.html.erb           # 认证布局
├── mailer.html.erb           # 邮件布局
├── pdf.html.erb              # PDF布局
└── shared/
    ├── _head.html.erb        # 头部共享部分
    ├── _nav.html.erb         # 导航共享部分
    └── _footer.html.erb      # 页脚共享部分

9.2 内容组织 #

erb
<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
<head>
  <%= render 'layouts/shared/head' %>
</head>
<body>
  <%= render 'layouts/shared/nav' %>
  
  <main>
    <%= render 'shared/flash' %>
    <%= yield %>
  </main>
  
  <%= render 'layouts/shared/footer' %>
  <%= render 'layouts/shared/javascripts' %>
</body>
</html>

9.3 性能优化 #

erb
<!-- 使用缓存 -->
<% cache @article do %>
  <%= render @article %>
<% end %>

<!-- 集合缓存 -->
<%= render partial: 'article', collection: @articles, cached: true %>

<!-- 条件渲染 -->
<%= render 'sidebar' if show_sidebar? %>

十、总结 #

10.1 核心要点 #

要点 说明
布局查找 控制器名 -> application
yield 内容插入点
content_for 定义命名内容
渲染选项 多种渲染方式
布局继承 控制器继承链

10.2 下一步 #

现在你已经掌握了布局与渲染,接下来让我们学习 表单构建器,深入了解Rails的表单系统!

最后更新:2026-03-28