Skip to content

Ruby Action View — ERB Templates Partials Helpers and Layouts Explained

DodaTech Updated 2026-06-28 9 min read

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>&copy; 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