Skip
Arish's avatar

38. Partials and Layouts


Partials and Layouts

Partials and layouts help you organize your views and keep them DRY. Layouts provide the common structure, while partials are reusable view fragments.

Layouts

Layouts wrap your views with common HTML structure:

erb
1<!-- app/views/layouts/application.html.erb -->
2<!DOCTYPE html>
3<html>
4<head>
5  <title><%= content_for(:title) || "My App" %></title>
6  <%= csrf_meta_tags %>
7  <%= csp_meta_tag %>
8  <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
9  <%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>
10  <%= yield :head %>
11</head>
12<body class="<%= controller_name %> <%= action_name %>">
13  <%= render "shared/navigation" %>
14  
15  <main class="container">
16    <%= render "shared/flash_messages" %>
17    <%= yield %>
18  </main>
19  
20  <%= render "shared/footer" %>
21</body>
22</html>

Multiple Layouts

ruby
1class AdminController < ApplicationController
2  layout "admin"
3end
4
5class ArticlesController < ApplicationController
6  layout "blog", only: [:index, :show]
7  
8  # Dynamic layout
9  layout :choose_layout
10  
11  private
12  
13  def choose_layout
14    current_user&.admin? ? "admin" : "application"
15  end
16end

Disabling Layout

ruby
1class ApiController < ApplicationController
2  layout false
3end
4
5# Or per action
6def show
7  render layout: false
8end

Content For

Pass content from views to layouts:

erb
1<!-- app/views/articles/show.html.erb -->
2<% content_for :title do %>
3  <%= @article.title %> - My Blog
4<% end %>
5
6<% content_for :head do %>
7  <meta name="description" content="<%= @article.excerpt %>">
8<% end %>
9
10<% content_for :sidebar do %>
11  <%= render "related_articles" %>
12<% end %>
13
14<article>
15  <h1><%= @article.title %></h1>
16  <%= @article.body %>
17</article>
erb
1<!-- In layout -->
2<head>
3  <title><%= yield :title %></title>
4  <%= yield :head %>
5</head>
6<body>
7  <main><%= yield %></main>
8  <aside><%= yield :sidebar %></aside>
9</body>

Check if Content Exists

erb
1<% if content_for?(:sidebar) %>
2  <aside><%= yield :sidebar %></aside>
3<% end %>

Partials

Partials are reusable view fragments. Their filenames start with an underscore:

erb
1<!-- app/views/articles/_article.html.erb -->
2<article class="article-card">
3  <h2><%= link_to article.title, article %></h2>
4  <p><%= truncate(article.body, length: 150) %></p>
5  <footer>
6    By <%= article.user.name %>7    <%= time_ago_in_words(article.created_at) %> ago
8  </footer>
9</article>

Rendering Partials

erb
1<!-- Basic render -->
2<%= render "article" %>
3<!-- Looks for _article.html.erb in current view folder -->
4
5<!-- With explicit path -->
6<%= render "shared/header" %>
7<%= render "articles/article" %>
8
9<!-- With local variables -->
10<%= render "article", article: @article %>
11<%= render partial: "article", locals: { article: @article } %>

Rendering Collections

erb
1<!-- Long form -->
2<% @articles.each do |article| %>
3  <%= render "article", article: article %>
4<% end %>
5
6<!-- Short form (Rails magic) -->
7<%= render @articles %>
8<!-- Renders _article.html.erb for each article -->
9
10<!-- Explicit collection -->
11<%= render partial: "article", collection: @articles %>
12
13<!-- Custom variable name -->
14<%= render partial: "article", collection: @articles, as: :post %>
15
16<!-- With spacer template -->
17<%= render partial: "article", collection: @articles, spacer_template: "separator" %>
18
19<!-- Empty state -->
20<%= render @articles || render("empty_state") %>

Passing Objects

erb
1<!-- When partial name matches model -->
2<%= render @article %>
3<!-- Renders app/views/articles/_article.html.erb with local variable 'article' -->
4
5<!-- This is equivalent to -->
6<%= render partial: "articles/article", locals: { article: @article } %>

Common Partial Patterns

Form Partial

erb
1<!-- app/views/articles/_form.html.erb -->
2<%= form_with model: article do |f| %>
3  <% if article.errors.any? %>
4    <div class="errors">
5      <% article.errors.full_messages.each do |msg| %>
6        <p><%= msg %></p>
7      <% end %>
8    </div>
9  <% end %>
10  
11  <div class="field">
12    <%= f.label :title %>
13    <%= f.text_field :title %>
14  </div>
15  
16  <div class="field">
17    <%= f.label :body %>
18    <%= f.text_area :body %>
19  </div>
20  
21  <%= f.submit %>
22<% end %>
erb
1<!-- new.html.erb -->
2<h1>New Article</h1>
3<%= render "form", article: @article %>
4
5<!-- edit.html.erb -->
6<h1>Edit Article</h1>
7<%= render "form", article: @article %>
erb
1<!-- app/views/shared/_navigation.html.erb -->
2<nav class="navbar">
3  <%= link_to "Home", root_path, class: nav_link_class(root_path) %>
4  <%= link_to "Articles", articles_path, class: nav_link_class(articles_path) %>
5  
6  <% if user_signed_in? %>
7    <span>Welcome, <%= current_user.name %></span>
8    <%= button_to "Log Out", logout_path, method: :delete %>
9  <% else %>
10    <%= link_to "Log In", login_path %>
11  <% end %>
12</nav>

Flash Messages Partial

erb
1<!-- app/views/shared/_flash_messages.html.erb -->
2<% flash.each do |type, message| %>
3  <div class="alert alert-<%= type %>" data-controller="dismissable">
4    <%= message %>
5    <button data-action="click->dismissable#dismiss">×</button>
6  </div>
7<% end %>

Card Component Partial

erb
1<!-- app/views/shared/_card.html.erb -->
2<div class="card">
3  <% if local_assigns[:image] %>
4    <img src="<%= image %>" class="card-img">
5  <% end %>
6  
7  <div class="card-body">
8    <h3 class="card-title"><%= title %></h3>
9    <% if local_assigns[:subtitle] %>
10      <p class="card-subtitle"><%= subtitle %></p>
11    <% end %>
12    <%= yield if block_given? %>
13  </div>
14</div>
erb
1<%= render "shared/card", title: "My Card", subtitle: "Optional" do %>
2  <p>Card content goes here</p>
3<% end %>

Local Assigns

Check if a local variable was passed:

erb
1<!-- _article.html.erb -->
2<article class="<%= local_assigns[:class] || 'default' %>">
3  <h2><%= article.title %></h2>
4  
5  <% if local_assigns[:show_author] %>
6    <span>By <%= article.user.name %></span>
7  <% end %>
8  
9  <%= truncate(article.body, length: local_assigns[:length] || 150) %>
10</article>
erb
1<%= render "article", article: @article, show_author: true, length: 300 %>

Nested Layouts

erb
1<!-- app/views/layouts/admin.html.erb -->
2<%= content_for :body do %>
3  <div class="admin-wrapper">
4    <%= render "admin/sidebar" %>
5    <main class="admin-content">
6      <%= yield %>
7    </main>
8  </div>
9<% end %>
10<%= render template: "layouts/application" %>

Partials and layouts are essential for building maintainable Rails views!