Boring Rails

Tip: Use Rails `cycle` to avoid `i % 2 == 0` in your view loops

:fire: Tiny Tips

Sometimes you need to keep track of how many times you’ve looped when rendering some views, for instance to alternate background colors to create a “striped” table.

<!-- @foods = ["apple", "orange", "banana"] -->

<% @foods.each do |food| %>
  <tr class="???">
    <td><%= food %></td>
  </tr>
<% end %>

You might try using :odd or :even CSS child selectors or switching to each_with_index.

<% @foods.each_with_index do |food, i| %>
  <tr class="<%= i % 2 == 0 ? 'bg-gray-200' : 'bg-gray-100' %>">
    <td><%= food %></td>
  </tr>
<% end %>

You could even refactor a bit to use i.odd? or i.even?.

<% @foods.each_with_index do |food, i| %>
  <tr class="<%= i.even? ? 'bg-gray-200' : 'bg-gray-100' %>">
    <td><%= food %></td>
  </tr>
<% end %>

Rails offers a different helper that comes in handy for these situations: cycle.

Usage

The cycle helper takes an array of arguments and loops through them each time it is called.

We could replace the above code with:

<% @foods.each_with_index do |food, i| %>
  <tr class="<%= cycle('bg-gray-200', 'bg-gray-100') %>">
    <td><%= food %></td>
  </tr>
<% end %>

The real benefits start to appear if you need more than two options.

<% @foods.each_with_index do |food, i| %>
  <tr class="<%= cycle('bg-red-100', 'bg-orange-100', 'bg-yellow-100') %>">
    <td><%= food %></td>
  </tr>
<% end %>

If you need to manually reset or share a cycle between code, you can pass a name: key as an option.

cycle("red", "white", "blue", name: "colors")
cycle("sm", "md", "lg", "xl", name: "sizes")

reset_cycle("colors")
reset_cycle("sizes")

You can also use any object that responds to to_s in the cycle.

<% @items.each do |item| %>
  <div class="rotate-<%= cycle(0, 45, 90, 135, 180) %>">
    <%= item %>
  </div>
<% end %>

Additional Resources

Rails API Docs: TextHelper#cycle

Tailwind Docs: Even/odd variants

If you like these tips, you'll love my Twitter account. All killer, no filler.