Rails Views and Partials: ERB, Layouts, and Fragments
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.
intro context
The simplest way to think about Rails views is to treat them as the last step before the browser sees anything. Controllers decide what data should be available, and views decide how to present that data cleanly. Once you keep that boundary in mind, template code becomes easier to read because you can tell what belongs in the response and what belongs somewhere else.
Rails also gives you a few tools that keep presentation code manageable as the app grows. Layouts handle the shared wrapper, partials handle repeated fragments, and helpers handle small formatting tasks. If you use those pieces with restraint, your view layer stays easy to scan instead of turning into a pile of embedded logic.
For the wider Rails flow, see the Rails routing tutorial and the Rails MVC pattern tutorial.
tl;dr
Rails views turn controller data into HTML. Use ERB for direct Ruby interpolation, layouts for shared wrappers, and partials for repeated fragments. The cleanest templates keep presentation logic small and push real decision-making into helpers or the controller.
how views fit into the request lifecycle
Rails follows the Model-View-Controller (MVC) pattern. When a user requests a URL like /posts, the request goes through several steps:
- The router maps the URL to a controller action (e.g.,
PostsController#index) - The controller processes the request, queries the model, and sets instance variables
- 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:
| Tag | What 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:
The equals sign in the ERB tag is the key difference. Without it, the code runs but nothing appears in the rendered page. The example below shows how iteration uses non-output tags while the actual values like post titles and body text use output tags to inject content into the HTML.
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.
Default rendering by convention keeps the controller code clean, but you will often need more control. The render method accepts several options: you can render a different template, return plain text, produce JSON for an API response, or render a partial with a specific object. Each option gives you a different way to control what the browser receives.
class PostsController < ApplicationController
def index
@posts = Post.all
@page_title = "All Posts"
end
end
In app/views/posts/index.html.erb:
Any instance variable set in the controller is available in the view automatically. This is the main mechanism for data transfer in Rails. What views should not do is equally important: avoid direct database calls, raw params access, and complex business logic in templates. Those responsibilities belong in the controller, model, or a dedicated helper.
<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.
view helpers and presentation logic
Helpers are a good fit for small formatting tasks that would otherwise repeat across templates. A helper can format a timestamp, decide which CSS class to use, or shorten a long label without making the view harder to read. That keeps the template focused on structure instead of forcing every page to repeat the same tiny decisions.
The line to watch is complexity. If a helper starts checking multiple models, branching on app rules, or transforming large data sets, it is probably doing too much. In that case, move the logic closer to the controller or into a presenter so the template stays easy to scan.
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:
A layout wraps every page in your application, and the yield keyword is where the main content gets inserted. The provide method sets a value only once — useful for page titles where you do not want a nested view accidentally overwriting a parent title. For content blocks that differ per page, content_for works similarly but overwrites on repeated calls.
<% 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:
When you pass an object to a partial, Rails makes it available as a local variable named after the partial file. Note that the variable is post, not @post — using instance variables inside partials creates an invisible dependency on the controller that breaks when you render the same partial from a different context.
<%= 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:
The shorthand render @post automatically looks for the matching partial file and passes the object as a local variable. This works because Rails infers the partial name from the object’s model name, which keeps view code concise when you follow the naming conventions consistently.
<%= 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:
When you have a collection of objects to render, Rails handles the iteration for you. This is both cleaner and more performant than writing the loop yourself because Rails can use a cached template renderer for collections. Inside the partial, each item is available as the local variable named after the partial.
<%= 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:
Spacer templates solve the common problem of needing something between each item in a list. Keep spacers small — an HR tag or a wrapper DIV — because they run between every pair of items and any complexity gets multiplied by the collection size.
<%= 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.
render | redirect_to | |
|---|---|---|
| HTTP response | 200 OK | 302/301 |
| Browser URL | Stays the same | Changes to new URL |
| Instance variables | Preserved | Lost — new request |
| Use when | Same view should redisplay | Need 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.
summary
Rails views are the presentation layer, ERB lets you mix Ruby into HTML, layouts handle the shared frame, and partials reduce repetition when the same fragment appears in more than one place. If a template starts looking like a controller, that is usually a sign to move code out of the view and into a better layer.
Next steps
After you are comfortable with layouts and partials, the next step is to see how data reaches those templates from the rest of the app. The Rails migrations tutorial shows how schema changes shape the records your views display, while the ActiveRecord basics tutorial shows where the data comes from before a view renders it. That connection makes the view layer easier to reason about because you can follow a record from the database to the page.
See Also
- Rails Routing — how URLs map to controller actions before a view is ever rendered
- ActiveRecord Basics — the model layer that typically feeds data to your views
- The MVC Pattern in Rails — how the three pieces connect