Cleaning up rails view logic using Draper

Rails makes it super easy to create database backed applications fast, but sometimes we get sloppy in all the hurry.

First iteration (quick and dirty)

Assume we have a simple blog application (yeah, original, I know) with a BlogPost model. The BlogPost model has three properties: title, content and author, all of which are strings. Our scaffolded index view looks like this:

<% @blog_posts.each do |blog_post| %>
  <tr>
    <td><%= blog_post.title %></td>
    <td><%= blog_post.content %></td>
    <td><%= blog_post.author %></td>
    ... other code to display in each line
  </tr>

We now want to display when the post was published, and our first stab at it looks like this:

    <td><%= blog_post.created_at %></td>

While this does satisfy the core requirement, the datetime format is rather verbose. In fact, we only want to show the date (not time) for the index page. Our first attempt at this looks like the following:

<% @blog_posts.each do |blog_post| %>
  <tr>
    ... previous lines
    <td><%= blog_post.created_at.strftime("%m.%b.%Y") %></td>
    ... other code to display in each line
  </tr>

We also want to show the published time when viewing an individual post, but this time we want to include the time (hours and minutes only).

    <p><%= blog_post.created_at.strftime("%m.%b.%Y %H:%M") %></p>

Now the timestamps are displayed just the way we want them.

Iteration 2: reducing view logic

The problem with the above approach is that we now have logic in our view. Not only does this clutter the view code, but if we want to use the same date format for blog posts elsewhere in the app, we have to duplicate this code everywhere.

Our first attempt to clean this up is to add a new method to the BlogPost model which we can then call instead of #created_at:

class BlogPost < ActiveRecord::Base
  def short_date
    created_at.strftime("%m.%b.%Y")
  end

  def long_date
    created_at.strftime("%m.%b.%Y %H:%M")
  end
end

Which allows us to change the index view to:

  <td><%= blog_post.short_date %></td>

and our show view to:

  <p><%= @blog_post.long_date %></p>

This looks much better.

Third iteration: remove presentation logic from model

While our views are certainly cleaner, we just added view specific logic to our model. The model should not be concerned about how to format data for particular views. So, where then do we put it? The answer is Decorators.

For this I will rely on one of my favourite gems, namely Draper.

We’ll create a new decorator, simply called BlogPostDecorator. This will be our place for view specific logic.

class BlogPostDecorator < Draper::Decorator
  delegate_all

  def short_date
    object.created_at.strftime("%m.%b.%Y")
  end

  def long_date
    object.created_at.strftime("%m.%b.%Y %H:%M")
  end
end

To make this available in the views, we must decorate the objects returned by the controller:

  def index
    @blog_posts = BlogPost.all.decorate
  end

  def show
    @blog_post = BlogPost.find(params[:id]).decorate
  end

Conclusion

We have met our view requirements with cleaner views and cleaner models by using decorators. I have merely shown you a glimpse of what Draper is capable of and I encourage you to head over to the Draper Github page and look through it.

If you want to play around with this, you can take a look at the source code for the BlogPost app.

3 thoughts on “Cleaning up rails view logic using Draper

  1. Shouldn’t it be (to respect I18n)


    class BlogPostDecorator < Draper::Decorator
    delegate_all

    def short_date
    helpers.l object.created_at, format: :short
    end

    def long_date
    helpers.l object.created_at
    end
    end

  2. Howdy! This blog post could not be written much better!
    Looking at this article reminds me of my previous roommate!

    He always kept preaching about this. I will send this
    post to him. Pretty sure he’ll have a great read.
    I appreciate you for sharing!

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">