MikeRogers0/MikeRogersIO

View on GitHub
src/_posts/2018-03-24-using-decorators-in-rails.md

Summary

Maintainability
Test Coverage
---
layout: post
title: Using Decorators in Rails
description: Avoid adding if/else logic inside view, instead use Decorators.
---

Have you ever opened a view in your Ruby on Rails app and seen something like this:

```erb
# users/show.html.erb
<% if user.avatar.present? %>
  <%= image_tag(user.avatar) %>
<% else %>
  <div><%= user.fullname %></div>
<% end %>
```

This is a somewhat common code smell, and should be avoided.

## What's wrong with if/else in a view?!

It adds extra cognitive processing while considering the design of a page. 

Instead of "Ok, when no avatar is present, it will always fall back to the users name in a div", the design choice becomes "It could fallback, but that might not always be the case".

## Enter Decorators

Decorators (I'm fond of [Draper](https://github.com/drapergem/draper)) take that presentational logic and puts it somewhere out of the way.

So the above code can become:

```ruby
# app/decorators/user_decorator.rb
class UserDecorator < Draper::Decorator
  delegate_all

  def branding
   if object.avatar.present?
     h.image_tag(object.avatar)
   else
     h.content_tag(:div, user.fullname)
   end
  end
end
```

```erb
# users/show.html.erb
<%= user.branding %>
```

## Expanding it further

The above example will get you pretty far, but you can reduce the amount of logic you're working with further.

### ApplicationDecorator

I've found adding a parent class containing commonly used methods can reduce the amount of duplication in decorators, e.g.:

```ruby
# app/decorators/application_decorator.rb
class ApplicationDecorator < Draper::Decorator

  private
  # Call cms_content(object.some_field) to have a consistent CMS output.
  def cms_content(body)
    h.sanitize(body, tags: %w(p a ul ol li), attributes: %w(href))
  end
end
```

### Contextual Decorators

In cases (such as admin panels), you might want to have different decoration while still inheriting from the parent decorators, e.g.:

```ruby
# app/decorators/admin/user_decorator.rb
class Admin::UserDecorator < UserDecorator
  # Tell the Draper we're decorating the User model.
  decorates User
  delegate_all

  # Fallback to gavatar image in the admin panels
  def avatar
    if object.avatar.present?
      h.image_tag(object.avatar)
    else
      h.image_tag(object.gavatar_url)
    end
  end
end
```