#286 Draper

Clean up complex view logic using Draper. This gem provides decorators (much like presenters) which bundles view logic in an object oriented fashion. In this episode I do a step-by-step refactoring of a complex template into a decorator.

In this episode we’ll take a look a Draper, a gem that lets us add decorators to a Rails application’s views much like a presenter pattern. If you find that you have a lot of complex view logic in your templates and helper methods Draper can help to clean up this code by taking a more object-orientated approach. We’ll show you how it works in this episode.

The application we’ll be working with is shown below. It has a user profile page that shows various pieces of information about a given user including their avatar, full name, username, a short biography in Markdown and links to a website and Twitter feed. If a user has supplied a website the avatar and full name will link to that site.

The page seems fairly simple but we also have to handle those users who haven’t entered so much data such as “MrMystery”.

This user has only entered a username so we display that in place of their full name, show a default avatar and some placeholder text for the other fields. This makes the template for this page more complex, with many if conditions needed to handle users with different amounts of information. We could make this template much cleaner if we could move some of this logic out to somewhere else.

As this logic is view-related we can’t really extract it out into the model. One solution for this would be to use helper methods. We already use one called image_tag in this template to render the avatar. Let’s take a look at it.

This helper method determines whether the current user has an avatar and returns the name of a default image if they don’t. We could extract more of the logic from the view into helper methods but the problem with is that they’re simple methods in a global namespace; there’s nothing object-orientated about them.

Installing Draper

This scenario is a good case for using a presenter, or a decorator as Draper refers to them, so let’s add it to our application. The Draper gem is installed in the usual way, by adding it to the Gemfile then running bundle.

As this is our first decorator an application_decorator will also be generated. Any decorators we generate inherit from ApplicationDecorator so we can place any functionality that we want to share across decorators there.

The UserDecorator class is fairly straightforward, consisting mainly of comments that explain how it works. We’ll dive right in and start using it to clean up our templates.

Tidying Up The Profile Page

To use Draper in our profile page we first need to make a change to the show action in the UsersController. This action currently fetches a User in the usual way.

We need to wrap this user in our decorator which we do by replacing User.find with UserDecorator.find.

/app/controllers/users_controller.rb

defshow@user = UserDecorator.find(params[:id])
end

This code will now return a UserDecorator instance that wraps the User record and delegates all methods to it by default (more on this later). The action will still work as it did before even though we’re working with a UserDecorator instead of a User. Now we can start to clean up our views and we’ll begin with the code that renders the user’s avatar.

This code will look for an avatar method in the UserDecorator which we’ll write next. There are some things we’ll need to be aware of when writing this method. Whenever we call a helper method from a decorator, such as our link_to_if method, we need to call it through the h method (this stands for “helpers”). When we want to reference the model we call model instead of, in this case, @user.

The code we’ve copied from the the view into avatar calls the avatar_name helper method. As we’re calling avatar_name from our decorator we’ll move it there from the UsersHelper class. Now that we have the method in the same class we don’t need to pass it a User and we can replace its calls to user with model.

We’ll need to write the linked_name method in the UserDecorator. There are similarities between the code we’ve taken from the template and the avatar method we wrote earlier; both of these render a link whose content is dependent on whether the user’s url if it’s present. As we’re in a class it’s easy to refactor this duplication out.

To handle the link creation we’ll create a new private method called site_link, which takes the content as a parameter. We can then call this method in both the avatar and linked_name methods to tidy them up. As before we replace any calls to @user in the linked_name with model. With all that done our decorator now looks like this.

As before we’ll create a method in the decorator class. We can see from the code that we’ve removed from the view that if the user has no url then some HTML is rendered. We could just return this as a string but we don’t want to put raw HTML into a Ruby string. Another solution would be to move the code into a partial and to render that, but as we only output a single HTML element it makes more sense to use the content_tag helper method.

We can do a similar thing for the two parts of the template that render the Twitter information and the user’s biography. We won’t show the details of that here, but after we’ve made the changes our view code will look a lot cleaner.

The two new methods look very similar to each other and also to the website method we wrote earlier. There’s a fair amount of duplication between the three methods, especially in each else clause, so it would be good if we could extract this part out into its own method.

We can use a block to help with this. We’ll extract the else clause out into its own method which we’ll call handle_none. We’ll pass the value we want to check the presence of to this method and also a block. If the value is present the code in the block will be executed, otherwise the span tag will be rendered. We can then use this handle_none to tidy up the website, twitter and bio methods.

Another change we could make is to extract the Markdown rendering into the ApplicationDecorator so that we can call it from any other decorators we might make. We’ll create a new markdown method there now that will render any text we pass to it.

Now in the UserDecorator we can now modify the bio method so that it calls markdown.

/app/decorators/user_decorator.rb

defbio
handle_none model.bio do
markdown(model.bio)
endend

Modifying The Model

Now that we have the decorator in place it’s a good idea to look through the model layer for any view-related code that we can move up to the relevant decorator. For example in our User model we have a member_since method that formats the user’s created_at time. This code can be considered view-related as all it does is return a formatted string so we’ll move it to the decorator.

All we need to do is move the method to the decorator and prepend model before created_at.

/app/decorators/user_decorator.rb

defmember_since
model.created_at.strftime("%B %e, %Y")
end

Restricting Access To The Model With The allows Method

While we’re modifying the UserDecorator there’s one more feature of Draper that we’ll demonstrate: the allows method. As it stands the UserDecorator will delegate all of its methods to the User object, but we can choose which methods are
delegated to the User model by using allows and passing it the name of the methods we want to delegate.

We’ll allow only username to be delegated and this way only the username method will be delegated down to the User model. This is the only method we need to delegate as it’s the only method that’s called in the view that doesn’t come from the decorator. This gives us more control over the decorator’s interface.

Now that we’re done with refactoring everything out to the decorator we’ll try loading a user’s profile page again to make sure that everything still looks the same.

It does. We can even check our other user and that still looks the same too but our view code is much cleaner.

By using a decorator our show template has been reduced from 1050 bytes in 34 lines to 382 bytes in 16 lines, a reduction in size of almost two thirds. It looks much cleaner too and we’ve made it much easier to edit should we want to change the layout of the page.