Ruby Action View — ERB Templates Partials Helpers and Layouts Explained
In this tutorial, you will learn about Ruby Action View. We cover key concepts, practical examples, and best practices to help you master this topic.
Ruby Action View is Rails' view layer that renders HTML templates using ERB (Embedded Ruby), with partials for reusable view fragments, helpers for view-logic extraction, and layouts for consistent page chrome across requests.
What You'll Learn
- Creating ERB templates with embedded Ruby
- Using layouts for consistent page structure
- Building partials for reusable components
- Writing helpers for view logic
- Rendering collections and forms
Why It Matters
Action View is what users see. Doda Browser uses Action View patterns for its settings pages and admin interfaces. Durga Antivirus Pro uses Action View for its cloud dashboard, reports, and configuration forms. Clean views make maintainable applications.
Real-World Use
Every Rails application uses views to render HTML. From GitHub's Repository pages to Shopify's storefronts, Action View powers the presentation layer of Rails applications worldwide.
flowchart LR
A["Action View"] --> B["Templates"]
B --> C["Layouts"]
C --> D["Partials"]
D --> E["Helpers"]
E --> F["Forms"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#dbeafe,stroke:#2563eb,color:#1e40af
style F fill:#f1f5f9,stroke:#94a3b8,color:#64748b
ERB Templates
ERB embeds Ruby code in HTML using <% and %> tags:
Output Tags
<!-- <%= %> — evaluates and outputs -->
<h1><%= @article.title %></h1>
<p>Posted <%= time_ago_in_words(@article.created_at) %> ago</p>
<p>By <%= @article.author.name %></p>
Execution Tags
<!-- <% %> — evaluates but doesn't output -->
<% if @article.published? %>
<span class="badge badge-success">Published</span>
<% else %>
<span class="badge badge-warning">Draft</span>
<% end %>
<% @articles.each do |article| %>
<div class="article-card">
<h2><%= link_to article.title, article_path(article) %></h2>
</div>
<% end %>
Layouts
Layouts provide consistent page structure:
<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
<head>
<title><%= yield(:title) || "My App" %></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>
<header>
<nav>
<%= link_to "Home", root_path %>
<%= link_to "Articles", articles_path %>
<% if current_user %>
<%= link_to "Profile", profile_path %>
<%= button_to "Logout", logout_path, method: :delete %>
<% else %>
<%= link_to "Login", login_path %>
<% end %>
</nav>
</header>
<main>
<% if flash[:notice] %>
<div class="alert alert-success"><%= flash[:notice] %></div>
<% end %>
<% if flash[:alert] %>
<div class="alert alert-danger"><%= flash[:alert] %></div>
<% end %>
<%= yield %>
</main>
<footer>
<p>© 2026 My App. Built by DodaTech.</p>
</footer>
</body>
</html>
Multiple Layouts
class AdminController < ApplicationController
layout "admin" # Uses app/views/layouts/admin.html.erb
end
class PrintController < ApplicationController
layout "print" # Minimal layout for printing
end
Partials
Partials are reusable view fragments:
<!-- app/views/articles/_article_card.html.erb -->
<article class="card">
<h3><%= link_to article.title, article_path(article) %></h3>
<p class="meta">
By <%= article.author.name %> |
<%= pluralize(article.comments_count, "comment") %>
</p>
<p><%= truncate(article.body, length: 200) %></p>
</article>
Rendering Partials
<!-- Render a collection -->
<h1>Articles</h1>
<%= render @articles %>
<!-- Renders app/views/articles/_article.html.erb for each article -->
<!-- Render with explicit partial -->
<%= render partial: "article_card", collection: @articles, as: :article %>
Shared Partials
<!-- app/views/shared/_pagination.html.erb -->
<div class="pagination">
<%= paginate @items %>
<span>Showing <%= @items.offset + 1 %> - <%= @items.offset + @items.length %> of <%= @items.total_count %></span>
</div>
<!-- In any view -->
<%= render "shared/pagination" %>
Local Variables in Partials
<%= render partial: "article_card", locals: { article: @article, show_full: true } %>
<!-- In _article_card.html.erb -->
<% if local_assigns[:show_full] %>
<%= simple_format(article.body) %>
<% else %>
<%= truncate(article.body, length: 200) %>
<% end %>
View Helpers
Helpers extract complex view logic:
# app/helpers/articles_helper.rb
module ArticlesHelper
def article_status_badge(article)
if article.published?
content_tag(:span, "Published", class: "badge badge-success")
else
content_tag(:span, "Draft", class: "badge badge-warning")
end
end
def article_meta(article)
"#{article.author.name} - #{time_ago_in_words(article.created_at)} ago"
end
def formatted_body(article)
simple_format(article.body)
end
end
Using Helpers in Views
<%= article_status_badge(@article) %>
<%= article_meta(@article) %>
<%= formatted_body(@article) %>
Built-in Helpers
<!-- Text helpers -->
<%= truncate(@article.body, length: 100) %>
<%= simple_format(@article.body) %>
<%= pluralize(@articles.count, "article") %>
<%= highlight(@article.body, "Rails") %>
<!-- Date helpers -->
<%= time_ago_in_words(@article.created_at) %> ago
<%= distance_of_time_in_words(@article.created_at, @article.updated_at) %>
<!-- URL helpers -->
<%= link_to "View", @article %>
<%= button_to "Delete", @article, method: :delete, data: { confirm: "Sure?" } %>
<%= mail_to "support@example.com", "Email Support" %>
<!-- Asset helpers -->
<%= image_tag "logo.png", alt: "Logo", size: "200x50" %>
<%= stylesheet_link_tag "application" %>
<%= javascript_include_tag "application" %>
<!-- Number helpers -->
<%= number_to_currency(19.99) %> <!-- $19.99 -->
<%= number_to_percentage(85.3) %> <!-- 85.300% -->
<%= number_to_human_size(1234567) %> <!-- 1.18 MB -->
Form Helpers
Form With (Rails 5.1+)
<%= form_with model: @article, local: true do |form| %>
<% if @article.errors.any? %>
<div class="error-messages">
<h2><%= pluralize(@article.errors.count, "error") %> prohibited this article:</h2>
<ul>
<% @article.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :title %>
<%= form.text_field :title %>
</div>
<div class="field">
<%= form.label :body %>
<%= form.text_area :body, rows: 10 %>
</div>
<div class="field">
<%= form.label :published %>
<%= form.check_box :published %>
</div>
<div class="field">
<%= form.label :category_id %>
<%= form.collection_select :category_id, Category.all, :id, :name %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
Form Input Types
<%= form.text_field :name %>
<%= form.password_field :password %>
<%= form.text_area :bio, rows: 5 %>
<%= form.check_box :active %>
<%= form.radio_button :role, "admin" %>
<%= form.select :country, ["US", "Canada", "Mexico"] %>
<%= form.file_field :avatar %>
<%= form.hidden_field :referrer %>
<%= form.date_field :birthday %>
<%= form.email_field :email %>
<%= form.number_field :age %>
<%= form.color_field :theme_color %>
<%= form.range_field :volume %>
View Inheritance
Views inherit from the controller's directory first, then fall back to views/application/:
<!-- Rendering app/views/articles/show.html.erb -->
<!-- If missing, falls back to app/views/application/show.html.erb -->
Content For and Yield
<!-- In a view -->
<% content_for :title do %>
<%= @article.title %> - My Blog
<% end %>
<% content_for :sidebar do %>
<h3>Related Articles</h3>
<%= render @related_articles %>
<% end %>
<!-- In layout -->
<title><%= yield(:title) || "My Blog" %></title>
<aside><%= yield(:sidebar) %></aside>
<%= yield %> <!-- Main content -->
Common Mistakes
1. Logic in Views
<!-- Bad — logic in view -->
<% if @user.role == "admin" && @article.published_at.present? && @article.category.present? %>
<span class="admin-article"><%= @article.title.upcase %></span>
<% end %>
<!-- Good — extracted to helper -->
<%= admin_article_title(@article) %>
2. N+1 Queries from Views
<!-- Bad — N+1 query -->
<% @articles.each do |article| %>
<p><%= article.author.name %></p> <!-- Queries author for each article -->
<% end %>
3. Missing Locals in Partials
<!-- Error — missing local variable -->
<%= render "article_card" %>
<!-- Correct — pass locals -->
<%= render "article_card", article: @article %>
4. Overusing Instance Variables
<!-- Hard to track where @variables come from -->
<%= render partial: "widget", locals: { data: @side_widget_data } %>
<!-- Better to pass explicitly -->
5. Not Escaping User Content
<!-- Dangerous — XSS vulnerability -->
<%= @article.body.html_safe %>
<!-- Safe — Rails auto-escapes -->
<%= @article.body %>
Practice Questions
1. What's the difference between <% and <%= in ERB?
<% %> evaluates Ruby code without outputting. <%= %> evaluates and outputs the result. Use <% %> for control flow, <%= %> for displaying values.
2. What is a partial and when would you use one?
A partial is a reusable view fragment stored in files starting with underscore. Use partials for repeated components (cards, forms, navigation) and collections.
3. How do layouts work in Rails?
Layouts wrap views with common HTML structure. The view content is inserted at <%= yield %>. Controllers specify their layout or inherit from ApplicationController.
4. What are helpers and why should you use them?
Helpers are modules that extract complex view logic from templates. They keep views clean, make logic testable, and allow reuse across views.
Challenge: Create a helper and partial for displaying a user's avatar with fallback to initials when no avatar is uploaded.
Solution
# app/helpers/users_helper.rb
module UsersHelper
def user_avatar(user, size: 40)
if user.avatar.attached?
render "shared/avatar_image", user: user, size: size
else
render "shared/avatar_initials", user: user, size: size
end
end
def user_initials(user)
user.name.split.first(2).map { |n| n[0].upcase }.join
end
end
<!-- app/views/shared/_avatar_image.html.erb -->
<%= image_tag user.avatar.variant(resize_to_fit: [size, size]),
alt: user.name,
class: "avatar",
style: "width: #{size}px; height: #{size}px; border-radius: 50%;" %>
<!-- app/views/shared/_avatar_initials.html.erb -->
<div class="avatar-initials"
style="width: <%= size %>px; height: <%= size %>px;
border-radius: 50%; background: #2563eb;
display: flex; align-items: center; justify-content: center;
color: white; font-weight: bold;">
<%= user_initials(user) %>
</div>
Usage in views:
<%= user_avatar(current_user, size: 50) %>
<%= user_avatar(@user, size: 100) %>
FAQ
{{< faq question="What is the difference between render and redirect_to?" >}}
render renders a template for the current request without a new HTTP request. redirect_to sends a 302/301 response telling the browser to make a new request. Use render for validation errors; use redirect_to after successful mutations.
{{< /faq >}}
{{< faq question="Can I use other template engines with Rails?" >}} Yes. Rails supports ERB (default), Haml, Slim, and others. Add the gem to your Gemfile and set the template handler. Haml and Slim use indentation-based syntax. {{< /faq >}}
{{< faq question="What is Turbolinks or Turbo Drive?" >}} Turbo Drive intercepts link clicks and form submissions, fetching the page via AJAX and replacing the body. It makes navigation feel faster by keeping the browser's JavaScript context alive across page loads. {{< /faq >}}
{{< faq question="How do I create a JSON view?" >}}
Use Jbuilder (render json: @article) or Active Model Serializers. For complex JSON structures, use jbuilder gem with .json.jbuilder template files.
{{< /faq >}}
{{< faq question="What is the asset pipeline?" >}} The asset pipeline (Sprockets) compiles, minifies, and serves CSS, JavaScript, and images. In modern Rails, use Import Maps or Webpacker/Shakapacker instead. {{< /faq >}}
Try It Yourself
<!-- app/views/articles/index.html.erb -->
<h1>Articles</h1>
<%= link_to "New Article", new_article_path, class: "btn btn-primary mb-3" %>
<div class="articles-grid">
<%= render @articles %>
</div>
<%= render "shared/pagination" %>
<!-- app/views/articles/_article.html.erb -->
<article class="card mb-3">
<div class="card-body">
<h2 class="card-title">
<%= link_to article.title, article_path(article) %>
</h2>
<p class="card-text text-muted">
By <%= article.author.name %> | <%= time_ago_in_words(article.created_at) %> ago
</p>
<p class="card-text"><%= truncate(article.body, length: 200) %></p>
<div class="d-flex justify-content-between">
<%= link_to "Read more", article_path(article), class: "btn btn-sm btn-outline-primary" %>
<span><%= pluralize(article.comments_count, "comment") %></span>
</div>
</div>
</article>
What's Next
Now that you understand views, learn about database migrations for managing schema changes in Rails.
| Topic | Description | Link |
|---|---|---|
| Ruby Migrations | Schema changes, data types, rollbacks | {{< ref "29-migrations" >}} |
| Ruby Active Record | ORM, queries, relationships | {{< ref "26-active-record" >}} |
| Python Django Templates | Compare Django's template system | Python |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro