A case for decorators in Rails

Decorators, at the beginning of my Rails journey, seemed exotic and unnecessary.

Draper, a popular gem for decorators, calls them an “object-oriented layer of presentation logic to your Rails application.” I struggled to see the practical benefit.

A particular use case sold me on decorators’ benefits.

Use case: default field values

TourExec is a Rails app that helps businesses take reservations online.

When customers make a reservation, they see a friendly introduction from the business. TourExec provides a default introduction, which a business can override with a custom one.

Screen Shot 2016-08-06 at 7.13.49 PM

In the database, the business’s intro field is blank unless they’ve added a custom introduction. We need to show the default intro if the field is blank, or show the custom intro if the field is filled.

You might wonder why I don’t store the default right in the database. The default text is dynamic, based on the business’s name (which can change), and I want the flexibility to retroactively change the default text.

Option 1: Use conditionals in views

I could use conditionals within views to show either the default or the custom intro:

- if @business.intro.present?
  = @business.intro
- else
  We're excited to have you on a tour at #{@business.name}!

This approach fell apart when I started building the form for businesses to update their intro message. I wanted businesses to see the default and be able to tweak or replace it.

Screen Shot 2016-08-06 at 7.18.33 PM

I had to hijack the form helper to show the default if the field was blank.

- f.input :intro, value: (
  (@business.intro.present? && @business.intro) ||
  "We're excited to have you on a tour at #{@business.name}!")

This felt messy and unclear.

Option 2: Use the model

I considered overriding the “intro” method in the Business model to return the default if the intro was blank.

# app/models/business.rb

...

def intro
  (attributes["intro"].present? && attributes["intro"]) ||
  "We're excited to have you on a tour at #{name}!"
end

It felt wrong to add presentation logic to the model. If I wanted to use HTML in the default message, I’d have to use raw HTML in the model or bring in ActionView helpers, which have no place in a model.

Option 3: Use a decorator

Decorators handle “presentation logic” for your models. That is, if a model’s fields should be shown in a certain way, the decorator is responsible for making that happen. Draper is a great tool for quickly implementing decorators.

First, I installed the draper gem. Then, I created a decorator for businesses.

# app/decorators/business.rb

class BusinessDecorator < Draper::Decorator
  delegate_all

  def intro
    if object.intro.present?
      object.intro
    else
      "Book a tour with #{object.name} below!"
    end
  end
end

The decorator introduces an intro method on top of the model. It can reach into the model’s intro method with object.intro to grab the actual field value.

When I set @business in my controllers, I have to decorate the object:

@business = current_user.business.decorate

Now, in views, I can reference the field as I normally would.

= @business.intro

And I have to do absolutely nothing to my form helpers:

- f.input :intro

Decorators won!

For this implementation of default field values, decorators seem the best option. I’d love your thoughts and feedback in the comments.

More about decorators

Decorators can do a lot more, like:

  • Show a date field in a particular format throughout your app
  • Show a status field with particular language throughout your app
  • Anything else that involves showing data from your models in a particular way

Check out the Draper docs for more information. Draper doesn’t have a monopoly on decorators; it just makes them faster to implement with a proven approach.


Hired.com brings Rails job offers to you. Free for candidates. Salaries from $75,000 to $250,000. Sign up now!

Leave a Reply