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 %>