Rails' Partial Features You (didn't) Know

3d rendering of toy blocks

Partials have been an integral part of Rails. They are conceptually simple to understand, but they pack quite a few smart and lesser known features you might not know about. Let’s look at all of them!

Basic rendering

Let’s look at the basics of rendering a partial.

<%= render partial: "application/navigation" %>

This will render the partial app/views/application/_navigation.html.erb. The filename starts with an underscore, but is referenced without one.

You can also omit the partial keyword.

<%= render "application/navigation" %>

So far nothing you didn’t know already, I assume.

Local Variables

You can pass variables like so.

<%= render partial: "user", locals: {user: @user}  %>

Again, this short-hand can also be used:

<%= render "user", user: @user %>

Or if you follow conventions from a to z, you could write this:

<%= render @user %>

This assumes a few things:

  • @user is present
  • a partial _user.html.erb in app/view/users.

Quite a bit cleaner, right? So when does one use the short-hand version and when not? Typically you can use the short-hand version, especially when you are only passing 1 or 2 variables. But you can also pass other arguments; then it’s required to use the longer syntax. Like this example:

<%= render partial: "user", locals: {admin: @admin}, as: :user %>

Explicit local variables

Introduced in Rails 7.2. You can set explicit local variables to clearly tell which variables are required.

<%# locals: (name:) -%>
<%= name %>

You can also set a default value. So if the user has no name set, it renders “Stranger”.

<%# locals: (name: "Stranger") -%>
<%= name %>

Local assigns

You can also use “implicit local variables”. Assume the above user partial is used in an admin view.

<div>
<p>
  <%= name %>
</p>

<% local_assigns[:admin?] %>
  <dl>
    <dt>
      Created at:
    </dt>
    <dd>
      <%= user.created_at %>
    </dd>
  </dl>
<% end %>
</div>

You can render the partial like so:

<%= render "user", user: @user, name: "Cam", admin?: true %>

Or omit the admin?: true in non-admin views:

<%= render "user", user: @user, name: "Cam" %>

This is useful, because without the local_assigns, rendering would fail.

Layouts for partials

You can also wrap a partial in a “layout”. This is not to be confused with view layouts stored in app/views/layouts.

<%= render partial: "user", layout: "shared/admin" %>

The admin “layout” is stored at app/views/shared/_admin.html.erb and could look like this:

<div class="admin">
  <%= yield %>
</div>

Render collection

If you want to render a collection of users, you can write the following, for example in app/views/users/index.html.erb:

<%= render partial: "user", collection: @users %>

Or its shorthand variant:

<%= render @users %>

This automatically looks up the _user.html.erb partial in app/views/users, assuming @users is a collection of User’s.

It doesn’t have to be a collection of one and the same Resource though, e.g. User. This too works:

<%= render [User.first, Admin.first, Customer.first, User.second] %>

This is assuming you have those resources, and each has their respective partial, e.g. _user.html.erb, _admin.html.erb and _customer.html.erb.

You are not limited to ActiveRecord models though. You can pass along any class as long as it responds to to_partial_path (this is what is used for all convential lookups, powered by ActionPack). Example:

# app/models/login.rb
class Login
  def to_partial_path
    "logins/login" # or whatever partial you want
  end
end

Or include ActiveModel::Conversation to get it for free:

# app/models/login.rb
class Login
 include ActiveModel::Conversion
end

Login.new.to_partial_path
# => "logins/login"

The latter option adds the option for you. See the source code on how this works (it adds not just to_partial_path).

Empty State

If the collection, e.g. @users is empty, render returns nil. So if you want to render an empty state, that is simple.

<%= render(@users) || "No users yet…" %>

Local variables

You can change the the local variable with a collection too.

<%= render partial: "user", collection: @users, as: :customer %>

In the _user.html.erb partial, you can now use customer.id (or whatever other attribute is available).

You also pass other variables:

<%= render partial: "user", collection: @users, locals: {admin?: true} %>

Caching

For long Rails made caching simple. Similar for collections:

<%= render partial: "user", collection: users, cached: true %>

This will cache each user’s partial. Make sure to run bin/rails dev:cache in development to try it.

Counter variables

You can render a counter too: <%= user_counter %>. The name, the part before the underscore, is derived from the partial name. So _customer.html.erb would be <%= customer_counter %>.

Layout for collections

Similarly to resource partials, you can also pass the layout option to collection-partials.

<%= render partial: @users, layout: "users/wrapper" %>

Then in app/views/users/_wrapper.html.erb, you could have something like this:

<ul id="users">
  <%= yield %>
</ul>

Spacer template

There is one more option for collections, which is spacer templates. It will be inserted between each instance. Use it like so:

<%= render partial: @users, spacer_template: "user/divider" %>

This will load the partial app/views/users/_divider.html.erb.

Bonus: Make partials look like components

If you have used ViewComponent, but can’t use them in your current project for whatever reason, here’s a way you make your partials quack a bit more like ViewComponent.

Create your components in app/view/components. For example a _card.html.erb.

<section class="card">
  <h4 class="card__header">
    <%= title %>
  </h4>

  <div class="card__body">
    <%= yield %>
  </div>
</section>

Then in your view use it like this:

<%= render layout: "components/card", locals: { title: "User Profile" } do %>
  <p>Just some text for this user</p>
<% end %>

But you probably want a helper to abstract some of that boilerplate away.

# app/helpers/components_helper.rb
module ComponentsHelper
  def component(name, **, &)
    render layout: "components/#{name}", locals: **, &
  end
end

Now you render it like this:

<%= component "card", title: "User Profile" do %>
  <p>Just some text for this user</p>
<% end %>

Pair that with the Explicit local variables and you got a pretty solid component-like set up without any dependencies.

And that are all the things you (didn’t) know about Rails’ partials. Which one was new to you?

Get UI & Product Engineering Insights for Rails Apps (and product updates!)

Published at . Last updated at . Have suggestions or improvements on this content? Do reach out.

UI components for Ruby on Rails apps

$ 129 one-time
payment

Get Access
  • One-time Payment

  • Access to the Entire Library

  • Built using ViewComponent

  • Designed with Tailwind CSS

  • Enhanced with Hotwire