Words and Code

One writer’s journey from words to code.

Tidying Up Those Views: Using Decorators in Rails

Last week, I had my first full-day technical interview. It was a thrilling experience, and so much more fun than I expected. Part of that was because I viewed the whole experience not as an evaluation, but rather an opportunity to learn as much as I could from experienced developers. I also knew that, irrespective of whether I did well or not, I’d learn some new technologies and tricks. And boy, was that the truth.

I left the interview with a long list of things I’d never heard of (why hello, blog posts for the next five Tuesdays!), one of which included the use of decorators in Rails. I was actually kind of surprised that I had never encountered decorators while building my own Rails apps, but it turns out that these are actually pretty advanced topics and there’s a good amount of debate on how and when to use decorators, and whether or not they’re actually useful.

But hey, none these discussions apply or make any sense unless you know what a decorator actually is. So, let’s find out!

It’s a well-known fact that your Rails model should be fat, and your controllers should be skinny. But what about your views? Well, there are varying schools of thought on the MVC framework and how it should function. Steve Klabnik argued that views should have no logic, stating that otherwise “They’re hard to test, they’re hard to read, and it’s not just a slippery slope, but a steep one. Things go downhill rapidly.”

I really liked the way that John Otander described how views should function in his blog post on decorators:

“Your views should be stupid. I like to use the analogy that views should read similarly to a shopping list. There shouldn’t be any complexity or logic.”

I decided to look back at Cabbie, one of my first projects, and check out the shape of my own views. This app basically was a review system (think Yelp) for New York City cab drivers. We had models such as Users, Drivers, Reviews, etc.

Here’s a taste of our driver show page:

1
2
3
4
5
6
7
8
<div class="driver-info">
  <%= @driver.medallion_number %>
  <% if @driver.medallion_number.length == 4 %>
    Medallion Number: <%= @driver.medallion_number %>
  <% else %>
    License Number: <%= @driver.medallion_number %>
  <% end %>
</div>


That’s pretty much the face I made when I looked at this code with a fresh set of eyes. This is one tiny little snippet of code, but imagine many more conditionals and much more logic in this view, and you get the idea. Not a pretty picture.

A couple things seemed problematic about this code:

  1. The first problem was that the view had to do the logic of figuring out whether a driver had a four-digit medallion number (NYC taxi) or a longer medallion number (an Uber driver). How could we fix this? Well, we could abstract this into a method in the class.
  2. The second problem: why is it the Driver object’s job to keep track of this? Do we really need a helper method in this case? All we really need this logic for is in rendering the view. We could make it its own class that inherits from the Driver class, maybe?
  3. Ok, the third problem: we know what we want – a Driver object that has some special functionality when it comes to presenting it in the view. But how do we get it? We want to extend the behavior of the Driver class purely for decorative purposes. Sounds like a job for a decorator!

So, what is a decorator, you might ask? Well, firstly, it’s actually known as the decorator pattern. An aspect of object-oriented programming, decorators allow you to add functionality to an object, but also retains all the other methods that belong to that object’s class. It’s a great tool to use when you want to implement and/or encapsulate certain methods that are only used in the view, and yet still have access to all the other properties of that object.

The best way to learn what a decorator does is to try and use it yourself and see what happens. I implemented my decorator on my Cabbie app, using the Draper gem.

First, you’ll want to make sure you add gem 'draper' to your Gemfile.

Next, you’ll want to generate the decorator for whatever object you want to ‘decorate’. In my case, I wanted to implement it on the driver object, so I typed rails generate decorator Driver into my terminal.

Then, add .decorate to your object in the appropriate controller. Here’s how I did it in my DriversController:

1
2
3
4
5
6
7
8
9
10
11
class DriversController < ApplicationController

  def show
    @driver = Driver.find(params[:id]).decorate
  end

  def search
    @driver = Driver.find_by(:medallion_number => params[:medallion_number].upcase).decorate
  end

end

After generating your decorator, you’ll notice that there’s a new directory in your /app file called decorators. Nothing to be scared of here: it’s just a class that inherits from the Draper Decorator gem. Here’s where you’ll add any additonal functionality you want your ‘decorated’ object to have.

In my case, I wanted to abstract away the Driver’s medallion/license number away and encapsulate it into a method. I created an id_number method in my decorator file:

1
2
3
4
5
6
7
8
9
10
11
12
class DriverDecorator < Draper::Decorator
  delegate_all

  def id_number
    if medallion_number.length == 4
      "Medallion Number: #{medallion_number}"
    else
      "License Number: #{medallion_number}"
    end
  end

end

Okay, you still with me? Here’s the satisfying part. Cut out all that ridiculous erb from your view – we don’t need that anymore! Instead, we’ll just call on the decorator method that we wrote for exactly this purpose. Ready? Okay. Let’s do this:

1
2
3
<div class="driver-info">
  <%= @driver.id_number %>
</div>

LOOK. AT. THAT. No iterations. No if/elsif/end keywords. One method, being called on exactly one object. No logic at all. You can read this and know exactly what we’re trying to render. Beautiful.

As lovely as this looks, it’s also important to keep in mind that this is an advanced topic and not always necessary. Only use decorators if they’ll actually clean up your code and be helpful.

And on that note, there’s only one thing left to do: Get tidying, kids!

tl;dr?

  • Using a decorator isn’t always the right answer. Make sure you’re comfortable with the MVC framework and that you really need to use a decorator before you go about implementing one.
  • If you want a more in-depth introduction to presenters and decorators, check out this RailsConf presentation by Mike Moore.
  • If this is still confusing, read more code examples; there are lots of really good blog posts that implement decorators.