Lightweight Proc Partials in Rails
I recently needed to display some content “cards” from dynamic content interspersed with static content on a Rails-backed website I was building for a client. Essentially, I needed something like this…
<div class="container">
<div class="person">John Smith</div>
<div class="person">Bill Nye</div>
<div class="interstitial">
<a href="/sign-up">Sign Up Now</a>
</div>
<div class="person">Carl Sagan</div>
</div>
…only where the people are filled in from the database. One solution is using CSS ordering instead of source ordering. This can work really well, but it takes additional time and can increase the cognitive overhead for reasoning about the behavior of a piece of code, especially for someone new to the project.
I wanted the source order to reflect the display order, but I didn’t want to repeat myself unnecessarily.
Verbose, and liable to trip me up in the future:
<div class="container">
<% @people.first(2).each do |person| %>
<div class="person"><%= person.name %></div>
<% end %>
<div class="interstitial">
<a href="/sign-up">Sign Up Now</a>
</div>
<% @people.drop(2).each do |person| %>
<!-- Duplicated above…gross -->
<div class="person"><%= person.name %></div>
<% end %>
</div>
I really don’t like repeating that .person
block, especially since my application is much more complex than that. Time is of the essence, and I don’t want to extract a view object or presenter, and a full on partial is overkill, especially given the performance penalty of rendering a partial in Rails.
Instead, I was able to capture the .person
template as a proc and called it with the relevant users in the appropriate order:
<% template = Proc.new do |person| %>
<div class="person"><%= person.name %></div>
<% end %>
<!-- Output first two people -->
<% @people.first(2).each(&template) %>
<div class="interstitial">
<a href="/sign-up">Sign Up Now</a>
</div>
<!-- Output remaining people -->
<% @people.drop(2).each(&template) %>
Surprisingly, this works, and it’s fast. Rails doesn’t need to parse another Erb file to render the template, and I don’t have the cognitive overhead of keeping the contents for the .person
template in another file.
If I were to be touching this view much in the future, however, I would want to refactor this a bit. Keeping the logic for rendering people around other content in the view is clumsy, and produces difficult to grok templates that are also fairly brittle.
For now though, onwards and upwards!