Cleaning up your views with Simple Delegators
While working on my pet project for matching tennis partners I decided to make a match history page. It displays a player's recent matches and some information pertaining to them: who played when, who won, and how much that win affected their score on the site. I also wanted each match history log to be separated by color-coded boxes.
This was the end result:
The first solution I came up with used a bunch of if
statements and some messy view logic:
# (most of the view omitted)
<% @matches.each do |match| %>
<div class=<% match.winner == current_user ? "winning_class" : "losing_class" %>>
<div><%= match.challenger %> vs <%= match.defender %></div>
<div><%= match.match_at %></div>
<% if match.winner == current_user %>
<div>+ <%= match.elo_difference></div>
<% else %>
<div>- <%= match.elo_difference></div>
<% end %>
<%= link_to "Match Details", match %>
</div>
<% end %>
But we're doing Ruby, and this is some ugly Ruby. There's a lot of logic in the views... and a ternary? Am I insane?
Enter Simple Delegators:
A concrete implementation of Delegator, this class provides the means to delegate all supported method calls to the object passed into the constructor and even to change the object being delegated to at a later time with
#__setobj__
.
Using this, we can make decorators to clean up our views.
Let's start with the controller:
def index
@matches = Match.all.map do |match|
if match.winner == current_user
WinningMatch.new(match)
else
LosingMatch.new(match)
end
end
end
Then we can make these decorated matches:
class WinningMatch < SimpleDelegator
attr_reader :match
def initialize(match)
@match = match
super(@match)
end
def match_outcome_class
"match-stats-winner"
end
def elo_delta
"+ #{match.elo_delta}"
end
def win_or_lose
"won"
end
end
We would do exactly the same for LosingMatch
.
We end up with an array of matches in our #index
action. Each match could either be a LosingMatch
or a WinningMatch
. Through the magic of duck typing, we can call the same method on either WinningMatch
or LosingMatch
and they'll know what to return.
So, in the end, we can create a partial like this:
<li>
<div>
<p><%= match.challenger_username %> vs. <%= match.defender_username %></p>
<p><%= match.match_at.strftime("%F") %></p>
<p><%= match.win_or_lose %></p>
<p><%= match.elo_delta %> </p>
<p><%= link_to "Match Details", match %></p>
</div>
</li>
This is pretty cool! We were able to take out the various if
statements and employ SimpleDelegator
s and duck typing to create a clean partial with absolutely no branching. I'd call that an enormous win.
Written by Elijah Kim
Powered by ⚡️ and 🤖.