Skip to content
ExponaBLOG
RUBY ON RAILS · DAY 016 / 100

Rails views and partials: a hands-on guide for engineers

Server-rendered HTML that stays organized.

Expona Admin7 min read

By day 16 of building with Rails, you've likely noticed that views quickly become messy without structure. The difference between a codebase where views stay organized and one where they spiral into maintenance nightmares often comes down to understanding how layouts, partials, helpers, and view components work together. This guide walks you through Rails views and partials with practical patterns we've used across production e-commerce and SaaS systems.

Layouts — application.html.erb wraps pages

Every Rails app starts with a layout. The default app/views/layouts/application.html.erb is the wrapper around your entire page—think of it as the HTML boilerplate that every view shares. Your controller automatically renders a view inside this layout unless you specify otherwise.

Here's a typical layout structure:

ruby
<!DOCTYPE html>
<html>
  <head>
    <title>Our 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>
      <%= render "shared/navbar" %>
    </header>

    <main class="container">
      <%= render "shared/flash_messages" %>
      <%= yield %>
    </main>

    <footer>
      <%= render "shared/footer" %>
    </footer>
  </body>
</html>

The yield keyword is critical—that's where the actual page content gets inserted. Every request to a controller action renders its view template inside this layout.

You can also create multiple layouts for different sections of your app. For example, an admin section might need a different header and sidebar:

ruby
class Admin::DashboardController < ApplicationController
  layout "admin"
end

And then create app/views/layouts/admin.html.erb. This keeps your admin interface visually and structurally separate from the main app.

A common pattern in production systems is using layouts to conditionally render navigation. If a user isn't logged in, show a public navbar; if they are, show their account menu. That logic belongs in the layout, not repeated in every view.

Partials — Reusable fragments with render

Partials are templates that you include within other templates. They're prefixed with an underscore but rendered without it. A partial is the Rails answer to DRY—when you have a component that appears on multiple pages or multiple times on one page, extract it to a partial.

Let's say you're building an e-commerce product listing. The product card appears on the home page, search results, and category pages. Extract it:

ruby
<!-- app/views/products/_product_card.html.erb -->
<article class="product-card">
  <img src="<%= product.image_url %>" alt="<%= product.name %>">
  <h3><%= link_to product.name, product %></h3>
  <p class="price"><%= number_to_currency(product.price) %></p>
  <button><%= link_to "Add to cart", cart_items_path(product_id: product.id), method: :post %></button>
</article>

Now render it wherever you need it:

ruby
<!-- app/views/products/index.html.erb -->
<div class="products-grid">
  <%= render "product_card", product: @product %>
</div>

When rendering a collection, Rails has a shorthand:

ruby
<div class="products-grid">
  <%= render "product_card", collection: @products, as: :product %>
</div>

This loops over @products, calling the partial once for each item and binding it to the local variable product. Rails also automatically provides a counter via product_card_counter if you need the index.

In large Rails apps, we organize partials into folders by feature. Create app/views/products/, app/views/orders/, and app/views/shared/ for cross-cutting UI components like modals, alerts, and form fields.

Partials can also accept options using the locals hash:

ruby
<%= render "product_card", product: @product, show_rating: true, hide_price: false %>

Then in the partial, use those local variables directly:

ruby
<!-- app/views/products/_product_card.html.erb -->
<article class="product-card">
  <img src="<%= product.image_url %>" alt="<%= product.name %>">
  <h3><%= product.name %></h3>
  <%= render "rating", product: product if show_rating %>
  <p class="price"><%= number_to_currency(product.price) %></p>
</article>

Helpers — Small presentation methods

Helpers are Ruby modules containing methods you call from views. They exist to keep view templates readable by moving presentation logic out of the ERB. Rails includes many built-in helpers like link_to, number_to_currency, and truncate. You also write custom helpers.

A common mistake is putting too much logic in views. Instead, write a helper:

ruby
# app/helpers/products_helper.rb
module ProductsHelper
  def product_badge(product)
    if product.on_sale?
      tag.span("Sale", class: "badge badge-danger")
    elsif product.new?
      tag.span("New", class: "badge badge-success")
    end
  end

  def product_availability_text(product)
    if product.in_stock?
      "In stock (#{product.quantity} available)"
    elsif product.back_order_available?
      "Back order: ships in #{product.estimated_days} days"
    else
      "Out of stock"
    end
  end
end

Now your view stays clean:

ruby
<!-- app/views/products/show.html.erb -->
<h1><%= @product.name %> <%= product_badge(@product) %></h1>
<p><%= product_availability_text(@product) %></p>

Helpers are scoped by default. A method in ProductsHelper is automatically available in products views. Use helper :other_helper in a controller to make another helper's methods available, or define methods in ApplicationHelper to make them global.

In production systems, we often create a ViewHelpers or PresentationHelper module for truly universal methods—formatting dates, handling missing data, rendering currency, building URLs. Keep helpers focused. A method doing multiple unrelated things is a sign you should split it.

View components — ViewComponent or Phlex for complex UI

As apps mature, you hit a wall with partials and helpers. Complex UI pieces that need their own state, tests, and encapsulation demand more structure. That's where view components come in.

ViewComponent is a gem that lets you write self-contained Ruby classes for view pieces. Each component is isolated, testable, and reusable:

ruby
# app/components/product_card_component.rb
class ProductCardComponent < ViewComponent::Base
  def initialize(product:, show_rating: false)
    @product = product
    @show_rating = show_rating
  end

  def call
    render :card
  end

  private

  def product_stock_status
    if @product.in_stock?
      "In stock"
    elsif @product.back_order_available?
      "Back order"
    else
      "Out of stock"
    end
  end
end
ruby
<!-- app/components/product_card_component/card.html.erb -->
<article class="product-card">
  <img src="<%= @product.image_url %>" alt="<%= @product.name %>">
  <h3><%= @product.name %></h3>
  <%= render ProductRatingComponent.new(product: @product) if @show_rating %>
  <p class="price"><%= number_to_currency(@product.price) %></p>
  <p class="status"><%= product_stock_status %></p>
</article>

Use it in views with the component tag:

ruby
<div class="products-grid">
  <%= render ProductCardComponent.new(product: @product, show_rating: true) %>
</div>

Or with the shorthand:

ruby
<div class="products-grid">
  <%= render ProductCardComponent.with_collection(@products, show_rating: true) %>
</div>

ViewComponent shines because each component is a class you can test in isolation, slot child content into, and manage with proper Ruby encapsulation. In a large SaaS dashboard, you might have dozens of components—modals, tables, form fields, notifications. ViewComponent keeps them organized and their dependencies clear.

Phlex is an alternative that builds HTML as Ruby objects, removing ERB entirely. It's powerful for developers who prefer pure Ruby and find ERB syntax cumbersome. Both ViewComponent and Phlex are worth exploring as your view layer grows.

Common mistakes

  • Too much logic in views: If you're writing conditionals, calculations, or database queries in ERB, extract them to helpers or components. Views should be almost 100% presentation.
  • Partials that are too granular: Don't create a partial for a single <div>. A partial should represent a cohesive, reusable piece of UI. If you're only using it once, it probably doesn't need to be a partial.
  • Not passing enough context to partials: Partials that rely on instance variables defined in the controller are fragile. Always pass locals explicitly. This makes dependencies obvious and partials reusable in unexpected places.
  • Forgetting to test view rendering: In production systems, we test views and components. Use `RSpec` with view specs or component specs. A broken view only shows up in the browser otherwise.
  • Mixing layout responsibility: Layouts should handle global structure and navigation. Page-specific styling or logic belongs in views or components, not layouts.

Key takeaways

  • Layouts wrap every page; use yield to insert page-specific content and keep shared elements (navbar, footer, flash messages) in one place.
  • Partials are your first tool for reusability; organize them by feature and always pass locals explicitly rather than relying on controller instance variables.
  • Helpers move presentation logic out of views, keeping templates readable and focused on structure and data display.
  • ViewComponent and Phlex bring object-oriented structure to complex UI pieces, making them testable, encapsulated, and easier to maintain as your app scales.
  • The pattern goes: layouts for global structure, partials for simple reusable fragments, helpers for presentation methods, and components for complex self-contained UI. Choose the right tool for each job.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles