Read more

Rails Partials

Niklas Hä.
March 07, 2023Software engineer at makandra GmbH

Rails partials have a lot of "hidden" features and this card describes some non-obvious usages of Rails Partials.

Rendering a basic partial

Illustration UI/UX Design

UI/UX Design by makandra brand

We make sure that your target audience has the best possible experience with your digital product. You get:

  • Design tailored to your audience
  • Proven processes customized to your needs
  • An expert team of experienced designers
Read more Show archive.org snapshot

The most basic way to render a partial:

render partial: 'partial' 

This will render a _partial.html.erb file. Notice how all partials need to be prefixed with _.

It's possible to define local variables that are only defined in the partial template.

# _weather.html.erb
<h1>The weather is <%= condition %></h1>

# index.html.erb
render partial: 'weather', locals: { condition: 'good' }

Since this is a common use-case, there's a shorthand way of rendering the partial and defining local variables:

# index.html.erb
render 'weather', condition: 'good'

Notice that it's not possible to mix the explicit partial: 'template' and the shorthand arguments for defining local variables. So either use longform variant or the shorthand variant, but don't mix them.

Another way of defining a local variable in a partial is using the object: keyword:

# _weather.html.erb
<h1>The weather is <%= weather.condition %></h1>

render 'weather', object: Weather.new(condition: 'bad')

It will automatically define a local variable named after the partial filename, in this example weather.

Using yield within a partial

It's possible for partials to call yield and act like a layout. You can use this to extract common containers in your HTML.

# _card.html.erb
<div class="card">
  <%= yield %>
</div>

# index.html.erb
<%= render partial: 'card' do %>
  <p>This is card content</p>
<% end %>
<%= render partial: 'weather', locals: { condition: 'good' }, layout: 'card' %>

# will render

<div class="card">
  <h1>The weather is good</h1>
</div>

Stick to the long-form for this one.

Rendering a partial in a controller

The examples above are for the common usage of rendering a partial in a template. It's also possible to render a partial in a controller:

class ExampleController < ApplicationController
  def show
    render partial: 'weather', locals: { condition: 'good' }
  end
end

Stick to the long-form partial: '...', locals: {...} for this one. This will render the partial without any layout.

Rendering collections

It's possible to pass a collection to render and it will render a partial for every item in the collection.

weathers = Weather.all.limit(3)
render partial: 'weather', collection: weathers, layout: 'card'

# will render

<div class="card"><h1>The weather is good</h1></div>
<div class="card"><h1>The weather is bad</h1></div>
<div class="card"><h1>The weather is nice</h1></div>

When rendering a collection, there will be two additional local variables assigned, which help you to do common tasks when rendering multiple items. In the example above, there will be the local variable weather_counter defined, which returns a counter of the currently rendered item in the collection. Furthermore it will add a weather_iteration object, with useful helper methods like first? or last?

Partials vs. Helpers

I recommend to avoid using helpers when rendering large chunks of html and go for a partial instead. If your helper method requires a large chunk of html, you can also render a partial within a helper.

#_mobile_navigation.html.haml
.mobile_navigation
  .htmlclass
    .htmlclass 
    .foo
      links.each do |link|
        = link_to(link)
        
       
module SomeHelper
  def render_mobile_navigation
    links = build_links
    render(partial: 'mobile_navigation', locals: { links: links })
  end 
end

Rendering empty states

Most screens should render something more sensible if a collection is empty. One can avoid ifs and any? calls in templates and make use of the return value of render.

<%= render(@collection) || render('empty_state') %>

Caching collections

Fragment caching a rendered collection is easily possible with render @collection, cached: true. Notice how this is more efficient than multiple normal cache @record calls, since this uses the Redis' mget command to request all cache keys in a single redis command.

Niklas Hä.
March 07, 2023Software engineer at makandra GmbH
Posted by Niklas Hä. to makandra dev (2023-03-07 10:44)