Refactoring Views for Clarity
I recently found myself doing some work on the views of an application I developed more than 18 months ago. As seems so often the case when looking at something I wrote long ago, I found myself somewhat dissatisfied with the way some of the code had been written. The partials I was looking at were DRY but long. In Ruby I’ve learned to extoll the virtues of shorter, more focused methods, and in Rails, by extension, I think these same virtues apply to smaller, more focused views.
The controller I was working on handled the usual RESTful
actions
for a resource named billable, but in addition to the usual
suspects, the billable resource also had several custom collection
actions named day
, week
and
month
. As you might guess from their names, they returned
collections of billables by day, week and month, respectively. The base
views were simple – they displayed a title and rendered a
_billables partial which displayed a table summarizing the
collection. The _billables partial was my primary concern
as it had grown too long. (See the original code below).
I decided to address the problem breaking the _billables
partial up. I created _head and _foot
partials to display the header and footer sections of the table, and I
created a _billable (singular) partial which displayed a
row for individual billables. I opted to shed some of the original
approach’s DRYness by replacing the one call to #render
in
each view with two calls for the _head and
_foot partials and a third call for the
_billable partial for the entire collection. (See these
changes below).
While I am not necessarily wed to the details of this new approach, I do think the result is a net gain. There are more files, more lines of code and there’s more duplication. But I believe everything is clearer. I also believe the duplication I’ve added is unlikely to add maintenance headaches – it’s hard to imagine it changing in any way that wouldn’t require significant updates with or without the duplication. (As long as I am using a table to display the collection, I will have a header, footer and main body).
These are the partials I started with:
<!-- day.html.erb -->
<h1>Billables | Daily</h1>
<%= render :partial => 'billables' %>
<!-- week.html.erb -->
<h1>Billables | Weekly</h1>
<%= render :partial => 'billables' %>
<!-- month.html.erb -->
<h1>Billables | Monthly</h1>
<%= render :partial => 'billables' %>
<!-- _billables.html.erb -->
<table>
<thead>
<tr>
<th></th>
<th>Date</th>
<th>Vendor</th>
<th>Project</th>
<th>Rate</th>
<th></th>
</tr>
</thead>
<tfoot>
<tr>
<td colspan="4">Total</td>
<td><%= number_to_currency @billables.to_a.sum(&:rate) %></td>
<td></td>
</tr>
</tfoot>
<tbody>
<% for billable in @billables %>
<% content_tag_for :tr, billable, :class => cycle('odd', 'even') do %>
<td>
<%= link_to 'view', billable %> |
<%= link_to 'edit', edit_billable_path(billable) %>
</td>
<td><%= billable.date.to_s(:short) %></td>
<td><%= h billable.vendor.name %></td>
<td><%= h billable.project.name %></td>
<td><%= number_to_currency billable.rate %></td>
<td>
<%= link_to 'delete', billable, :method => :delete %>
</td>
<% end %>
<% end %>
</tbody>
</table>
These are the partials I ended with:
<!-- day.html.erb -->
<h1>Billables | Daily</h1>
<table>
<thead><%= render :partial => 'head' %></thead>
<tfoot><%= render :partial => 'foot' %></tfoot>
<tbody><%= render :partial => @billables %></tbody>
</table>
<!-- week.html.erb -->
<h1>Billables | Weekly</h1>
<table>
<thead><%= render :partial => 'head' %></thead>
<tfoot><%= render :partial => 'foot' %></tfoot>
<tbody><%= render :partial => @billables %></tbody>
</table>
<!-- month.html.erb -->
<h1>Billables | Monthly</h1>
<table>
<thead><%= render :partial => 'head' %></thead>
<tfoot><%= render :partial => 'foot' %></tfoot>
<tbody><%= render :partial => @billables %></tbody>
</table>
<!-- _head.html.erb -->
<tr>
<th></th>
<th>Date</th>
<th>Vendor</th>
<th>Project</th>
<th>Rate</th>
<th></th>
</tr>
<!-- _foot.html.erb -->
<tr>
<td colspan="4">Total</td>
<td><%= number_to_currency @billables.to_a.sum(&:rate) %></td>
<td></td>
</tr>
<!-- _billable.html.erb -->
<% content_tag_for :tr, billable, :class => cycle('odd', 'even') do %>
<td>
<%= link_to 'view', billable %> |
<%= link_to 'edit', edit_billable_path(billable) %>
</td>
<td><%= billable.date.to_s(:short) %></td>
<td><%= h billable.vendor.name %></td>
<td><%= h billable.project.name %></td>
<td><%= number_to_currency billable.rate %></td>
<td>
<%= link_to 'delete', billable, :method => :delete %>
</td>
<% end %>