Views and Partials in Rails

· 6 min read · Updated March 28, 2026 · beginner
rails views erb web

Rails views are the presentation layer of your application. They take data from the controller and turn it into HTML that a browser can display. If you’re new to Rails, understanding how views work, how ERB templates fit together, and when to reach for a partial will save you from writing messy, hard-to-maintain templates.

How Views Fit Into the Request Lifecycle

Rails follows the Model-View-Controller (MVC) pattern. When a user requests a URL like /posts, here’s what happens:

  1. The router maps the URL to a controller action (e.g., PostsController#index)
  2. The controller processes the request, queries the model, and sets instance variables
  3. Rails automatically renders the view matching the controller and action name

By default, Rails looks for app/views/posts/index.html.erb when PostsController#index runs. You don’t need to explicitly tell Rails to render — it figures it out from the controller and action names.

The view never initiates a request on its own. It only reacts to data the controller passes along. That separation is by design.

ERB Templates: Embedding Ruby in HTML

ERB (Embedded Ruby) lets you mix Ruby logic into your HTML templates. You have two tag types to choose from:

TagWhat it does
<%= %>Runs Ruby and inserts the result into the HTML
<% %>Runs Ruby with no output (logic only)

The = is the key difference. Without it, the code runs but nothing appears in the rendered page.

<% @posts.each do |post| %>
  <article>
    <h2><%= post.title %></h2>
    <p><%= post.body %></p>
  </article>
<% end %>

<% if @posts.empty? %>
  <p>No posts found.</p>
<% end %>

Here, @posts.each uses <% %> because iterating doesn’t produce output. The post.title and post.body use <%= %> because those values need to appear in the HTML. The conditional uses <% %> since it only controls whether the <p> tag appears.

The render Method in Controllers

By default, Rails renders the view matching the current action. But sometimes you need more control. The render method gives you that.

class PostsController < ApplicationController
  def index
    @posts = Post.all
    # Rails auto-renders app/views/posts/index.html.erb
  end

  def show
    @post = Post.find(params[:id])
    render template: "posts/special_show"
    # Renders a different template instead
  end
end

You can render more than just templates. Common alternatives:

render plain: "Hello, world"
render json: @post
render xml: @post
render nothing: true
render partial: "posts/post", object: @post

Instance Variables: Passing Data From Controller to View

Any instance variable set in the controller is available in the view automatically. That’s the main mechanism for data transfer.

class PostsController < ApplicationController
  def index
    @posts = Post.all
    @page_title = "All Posts"
  end
end

In app/views/posts/index.html.erb:

<h1><%= @page_title %></h1>
<% @posts.each do |post| %>
  <%= render partial: "posts/post", object: post %>
<% end %>

What views should not do: avoid direct database calls, raw params access, or complex business logic in templates. That belongs in the controller, model, or a helper. Views are for presentation.

Layouts: Wrapping Every Page

A layout wraps every page in your application. The default layout is app/views/layouts/application.html.erb. Every controller uses this unless you specify otherwise.

<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield :title %></title>
    <%= csrf_meta_tags %>
  </head>
  <body>
    <nav><%= render "shared/nav" %></nav>
    <main>
      <%= yield %>
    </main>
    <footer><%= render "shared/footer" %></footer>
  </body>
</html>

yield renders the main page content. If you want to set page-specific content in the layout, use provide in your view:

<% provide :title, @post.title %>

<h1><%= @post.title %></h1>
<p><%= @post.body %></p>

Then in the layout, <%= yield :title %> renders the provided title. provide sets the value only once — if you call it twice, the first value wins. That’s useful for page titles where you don’t want a nested view accidentally overwriting a parent view’s title.

content_for works similarly but overwrites on repeated calls, which is handy for sidebars and content blocks that differ per page.

Partials: Reusable View Fragments

Partials let you extract reusable pieces of a template into separate files. They follow a naming convention: files start with an underscore.

Basic Partials

<!-- app/views/posts/_post.html.erb -->
<article class="post">
  <h3><%= post.title %></h3>
  <p><%= post.body %></p>
</article>

Render it from another view:

<%= render partial: "posts/post", object: @post %>

When you pass an object to a partial, Rails makes it available as a local variable named after the partial (here: post). Note that it’s post, not @post.

Shorthand for Rendering a Single Object

When the partial name matches the model, Rails gives you a shorter form:

<%= render @post %>

This automatically looks for app/views/posts/_post.html.erb and passes @post as the post local variable. It reads cleaner, especially when you know the convention.

Passing Local Variables Explicitly

Sometimes you want to pass multiple variables or give them specific names:

<%= render partial: "comments/comment",
           locals: { comment: @comment, show_author: true } %>

Inside _comment.html.erb, those are available as comment and show_author.

Rendering Collections Efficiently

Partials shine when rendering collections. Rails handles the iteration for you:

<%= render @posts %>

This renders _post.html.erb once for each post in @posts. Inside the partial, each post is available as the post local variable. This is both cleaner and more performant than writing the loop yourself.

You can also use the explicit form:

<%= render partial: "posts/post", collection: @posts, as: :post %>

The as: :post option names the local variable explicitly. Without it, Rails infers the name from the partial filename.

Spacer Templates

Need something between each item in a collection? Use spacer_template:

<%= render @posts, spacer_template: "shared/divider" %>

Rails renders _divider.html.erb between each _post.html.erb. Keep spacer partials small — an <hr> or a wrapper <div>.

render vs redirect_to: Knowing When to Use Each

This catches many Rails beginners. After form submissions especially, choosing correctly matters.

renderredirect_to
HTTP response200 OK302/301
Browser URLStays the sameChanges to new URL
Instance variablesPreservedLost — new request
Use whenSame view should redisplayNeed a different URL
def create
  @post = Post.new(post_params)
  if @post.save
    redirect_to @post  # Browser goes to PostsController#show
  else
    render :new        # Redraws the form with @post and its errors
  end
end

render :new keeps @post in memory with its validation errors intact. If you used redirect_to :new instead, Rails would fire a fresh request to new_post_path, and @post would be gone. The form would redisplay as a blank form with no error messages.

The same principle applies any time a form submission fails. If the data should stay on screen for correction, render it. If you need the browser to navigate somewhere else, redirect_to.

Common Mistakes to Avoid

Calling render after a redirect_to — Rails executes the first render or redirect_to and ignores subsequent ones, but this is a silent bug waiting to happen. If you see code that looks like redirect_to @post and return, the and return pattern is there to prevent exactly this.

Using @variable inside a partial instead of the local — When you pass object: @post, the partial receives post (no @). Using @post in the partial works if @post happens to be set in the controller, but it breaks when you render the same partial for multiple objects.

Placing complex logic in views — If you find yourself writing multi-line Ruby loops, conditionals, or method calls in a template, move that logic to a helper or a presenter. Views should be dumb about anything except presentation.

See Also