Skip to content

Instantly share code, notes, and snippets.

@trouni
Last active November 20, 2023 13:17
Show Gist options
  • Save trouni/40c2562892357ee374acf0e5f9558733 to your computer and use it in GitHub Desktop.
Save trouni/40c2562892357ee374acf0e5f9558733 to your computer and use it in GitHub Desktop.
Rails Partials Tips

Rails Partials Pro Tips

Best practices to keep you Rails views tidy and your components smart.

Pro Tip #1 πŸ’‘ When should you use a partial?

Think in terms of component. Can those few lines be reused somewhere else in your application? If the answer is yes, it should be in a partial.

A good rule of thumb when iterating using each is to always use a partial. If you have more than a few lines of HTML in the block of an each, you should probably have a partial, especially if those lines could be reused somewhere else (=component).

<% collection.each do |element| %>
  <!--  Not more than a few lines here... else use a partial! -->
<% end %>

Pro Tip #2 πŸ’‘ Use local variables, not @ instance variables

⚠️ Don't use instance variables in your partials

A partial may be used anywhere in your app. You can't count on always having an @instance variable set in the controller. Instead, explicitly pass the instance you need to render a partial.

❌ Don't do this:

<!-- app/views/posts/_card.html.erb -->
<h1><%= @post.title %></h1>

<!-- app/views/posts/show.html.erb -->
<%= render 'posts/card' %>

You can't use this partial:

  • on any page that doesn't have an @post in the controller, or even
  • in a simple iteration like @posts.each do |post| (you have a post, not a @post)!

βœ… Instead, do this:

<!-- app/views/posts/_card.html.erb -->
<h1><%= post.title %></h1>

<!-- app/views/posts/show.html.erb -->
<%= render 'posts/card', post: @post %>

Just like you would give an argument when calling a method, you give a post to your partial when you render (=call) it.

Pro Tip #3 πŸ’‘ How to name your model partials?

Rails has some nice conventions with filenames that you can use to clean up your views.

For example, each model usually has a main card partial. By saving your default post card under views/posts/_post.html.erb, instead of:

<%= render 'posts/post', post: @post %>

... you can now simply use:

<%= render @post %>

Even more powerful with collections!

render will automatically iterate over a collection of posts 🀯

<%= render @posts %>

is equivalent to

<% @posts.each do |post| %>
  <%= render 'posts/post', post: post %>
<% end %>

Pro Tip #4 πŸ’‘ Where to store shared partials?

According to the Rails documentation:

The lookup order for an admin/products#index action will be:

app/views/admin/products/
app/views/admin/
app/views/application/

Which means app/views/application/ is a convenient place to store all of the shared partials of your app.

For example, if you create a partial

app/views/application/_awesome_button.html.erb

you can render it with:

<%= render 'awesome_button' %>

Pro Tip #5 πŸ’‘ Make smart components by adding local variables (=parameters)

You can pass any kind of local variables to your partials, not just instances. For example:

<%= render 'avatar', user: user, large: true %>
<!-- app/views/users/_user.html.erb -->

<!-- ... -->
  <%= image_tag user.avatar_url, class: large ? 'avatar large' : 'avatar' %>

  <!-- or since Rails 6.1 -->
  <%= image_tag user.avatar_url, class: class_names('avatar', large: large) %>
<!-- ... -->

class_names is a new helper introduced in Rails 6.1 which is useful to add conditional classes to your HTML elements.

Pro Tip #6 πŸ’‘ Make local variables optional using local_assigns

The previous implementation of the partial will raise undefined variable if you don't set a value for large when rendering the partial. Ideally, large should be optional.

Implementing optional variables in partials

<!-- app/views/users/_user.html.erb -->

<!-- ... -->
  <%= image_tag user.avatar_url, class: local_assigns[:large] ? 'avatar large' : 'avatar' %>

  <!-- Or since Rails 6.1 -->
  <%= image_tag user.avatar_url, class: class_names('avatar', large: local_assigns[:large]) %>
<!-- ... -->

local_assigns is a hash that contains all the local variables passed to your partial. If a variable was not passed, it returns nil (like a normal hash would when accessing a key that doesn't exist).

Pro Tip #7 πŸ’‘ Create wrapper components with partials (advanced)

Sometimes, you need a standard wrapper to use with many different components. For example, a card wrapper containing a consistent structure and style for the cards throughout your app. However, the content inside the card wrapper needs to be flexible.

How can we avoid copy-pasting that same wrapper in every card partial...?

... with yield!! 🀯

Create a wrapper partial, and add yield where you want some content to be inserted later.

<!-- app/views/application/_card_wrapper.html.erb -->

<div class="card-wrapper">

  <% if local_assigns[:photo] %>
    <%= img_tag :photo, class: 'card-photo' %>
  <% end %>

  <div class="card-content">
    <%= yield %> <!-- content will be inserted here -->
  </div>

</div>

Create your card component, render and pass a block to the wrapper partial:

<!-- app/views/posts/_post.html.erb -->

<%= render 'card-wrapper', photo: post.photo_url do %>
  <h1><%= post.title %></h1>
  <p><%= post.summary %></p>
<% end %>

Now, you can just use <%= render post %> in your view, which will render:

<div class="card-wrapper">

  <% if post.photo_url %>
    <%= img_tag post.photo_url, class: 'card-photo' %>
  <% end %>
  
  <div class="card-content">
    <h1><%= post.title %></h1>
    <p><%= post.summary %></p>
  </div>

</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment