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.
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} %>
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?