March 20, 2015

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 %>
<% 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 = do |match|
    if match.winner == current_user

Then we can make these decorated matches:

class WinningMatch < SimpleDelegator
  attr_reader :match

  def initialize(match)
    @match = match

  def match_outcome_class

  def elo_delta
    "+ #{match.elo_delta}"

  def win_or_lose

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:

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

This is pretty cool! We were able to take out the various if statements and employ SimpleDelegators and duck typing to create a clean partial with absolutely no branching. I'd call that an enormous win.

Written by Elijah Kim © 2022.
Powered by NextJS and Vercel.