Experimenting with Draper
Let’s play around with the concept of decorators and check out some of the features offered by the Draper gem.
This tutorial is open source. Please contribute fixes or additions to the markdown source on GitHub.
Setup
Get the Blogger project from GitHub and run setup procedures:
1 2 3 4 5 | |
All existing tests should pass. Optionally, run the tests continuously while developing by running guard
Install Draper
Next, open the Gemfile and add a dependency on 'draper' like this:
1
| |
Run bundle, then start up your server.
Generate a Decorator
We’ll create a decorator to wrap the Article model. Draper gives you a handy generator:
Terminal
$
| |
It will create the folders app/decorators/, spec/decorators/and the files app/decorators/article_decorator.rb, spec/decorators/article_decorator_spec.rb. Open the file and you’ll find the frame of a ArticleDecorator class.
Restart your server so the new folder is added to the load path.
First Usage
Without adding anything to the decorator, let’s see the simplest usage. Open the articles_controller and look at the show action. It currently has:
1 2 3 | |
To make use of the decorator, call the .new method and pass in the Article from the database:
1 2 3 4 | |
Test it by displaying a single article in your browser and making sure things work as expected. The tests should also pass.
Using decorates_finders
The pattern of finding an object then immediately decorating it is a common one.
In the decorator you can use decorates_finders method like the following:
1 2 3 4 5 | |
Then the decorator will delegate the find method to the wrapped class, allowing us to write this:
1 2 3 | |
Then go and view the show page for a single Article by clicking on its name on the index.
Adding Methods
Now let’s add some useful functionality to our decorator.
Article Published On
Currently the show page just displays the raw created_at data in the "Published" line.
Often we want to standardize date formatting across our application, and the decorator makes this easy.
Let’s create a formatter method for created_at method in our decorator:
1 2 3 | |
The object method here refers to the instance of Article that the decorator wraps.
Using article
For better semantics, the ArticleDecorator creates an alias for object named article.
So we can replace object with article:
1 2 3 | |
Modifying the View Template
Now in the show.html.erb for Article you need to change the following line:
1
| |
to:
1
| |
Refresh your browser and the "Published" line will use the new formatting.
Overriding Existing Methods
You aren’t limited to defining new methods in the decorator. If the decorator defines a method then that method will be found first, even if the wrapped model has an identical method. This means you can effectively override methods of the wrapped object like so:
1 2 3 | |
Then return the view template to just:
1
| |
Comment Counter
Currently the show page uses the pluralize helper:
1
| |
That’s a bit of logic we can rip out of the view template. Let’s add a method
to the ArticleDecorator:
1 2 3 | |
Then in the view template:
1
| |
Notice how the comments_count method used h? That’s the method Draper offers
to access all the built in Rails view helpers. Without it you’d be calling
pluralize on the decorator which isn’t defined. With it, you get the same effect
as calling pluralize in your view template.
Dealing with a Collection
If you look in the index.html.erb, you’ll see a similar pluralize line.
Can you just reuse the decorator method? Try calling the .comments_count method
and you won’t get anywhere.
We need the article objects to be decorated in the index action of the controller.
Currently the index has:
1 2 3 | |
Let’s tweak it a bit to decorate the collection:
1 2 3 4 | |
Now all elements of the collection are decorated and our index should display properly.
Links
Delete links are a pain to write. Want your delete link to actually be an image? Double pain.
The show.html.erb is currently using a custom helper to generate the link with icon:
1
| |
Which calls this helper in IconsHelper:
1 2 3 4 5 6 7 | |
It works fine and wraps up some of the ugliness, but using the helper is a procedural approach. The decorator allows us to be object-oriented.
Writing ArticleDecorator#delete_icon
To rework this helper, let’s start by just copying & pasting the helper method into our decorator class:
1 2 3 4 5 6 7 8 9 10 11 | |
It won’t work as-is. We need to make some changes.
First, looking at the arguments, we don’t need to pass in object anymore because
the decorator will already have the article.
Then all the Rails helpers (like link_to and image_tag) need to rely on the
h helper. The result looks like this:
1 2 3 4 5 6 7 8 9 10 11 | |
Note how object became article in the call to polymorphic_path.
In the Show Template
Originally, we used a normal helper method:
1
| |
Now we can use the decorator method:
1
| |
Keeping it object-oriented is the Ruby way.
Abstracting Decorators
In the conversion from Helper to Decorator in the last section, something was lost. The helper method worked on any object passed in, the decorator method belonged to the ArticleDecorator. We definitely don’t want to re-implement this code in multiple decorators, so how can we make it shareable?
ApplicationDecorator.rb
A first approach is to make an app/decorators/application_decorator.rb and move the method in there:
1 2 3 4 5 6 7 8 9 | |
Then have the ‘ArticleDecorator’ inherit from ‘ApplicationDecorator’ (like class ArticleDecorator < ApplicationDecorator)
instead of Draper::Decorator.
It’ll work for what we have so far, but if we try and use this from a CommentDecorator, it’s going to blow up because of the call to polymorphic_path(article).
Draper provides generic ways to access the wrapped object – the object or model methods. Change article to object or model (they’re aliases for one another) and we’re good to go:
1 2 3 4 5 6 7 8 9 | |
The downside to this inheritance approach is that every decorator in the system is going to have this method. What if some decorators need a different style of delete icon?
The Module Approach
One of the limitations of normal custom helpers is that they all live in the same global name space. You can’t have two different implementations of a delete_icon helper in articles_helper and comments_helper. But since decorators are objects, that’s not an issue. We can use modules and mix them into the decorator classes.
For instance, we can create app/decorators/icon_link_decorations.rb and define this module:
1 2 3 4 5 6 7 8 9 | |
Remove the similar code from the ApplicationDecorator, and include the module from the ArticleDecorator:
1 2 3 4 | |
Any other decorators that want to use that method can similarly include the module.
Experiments
Now that you’ve got the basics down, try the following experiments on your own:
- Can you relocate
IconsHelper#edit_iconlike you did thedelete_icon? - In the
articles/show.html.erbthere’s<%= link_to article.title, article_path(article) %>. Could you rework this into<%= article.link %>? - Check out the
TagsHelper. Can you rewrite any/all of these by creating aTagDecorator?
Generating XML and JSON with Decorators
We usually need to_json and to_xml operations to present an API. They’re often implemented in the model, but they really belong in the view layer because they’re presentation concerns. The decorator pattern can help us clean up responsibilities.
Proxying to_json
As a first step, implement a to_json method in the ArticleDecorator that just calls the ActiveRecord method. Add support in the controller to render this JSON out to the browser.
Per-User Scoping
It would be great to scope the JSON based on the current user. We’d want admin users to see all the article attributes in the JSON, but public users would just get a subset.
This Blogger doesn’t have authentication/authorization setup, so we’ll fake it using a request parameter.
- Define two constants in the decorator:
PUBLIC_ATTRIBUTESas an array containing symbols for thetitle,body, andcreated_atADMIN_ATTRIBUTESas an array containing everything fromPUBLIC_ATTRIBUTES, plusupdated_at
- Manually add a parameter to your request URL with
admin=true - Write a
current_user_is_admin?method in yourApplicationHelperwhich returns true if that parameter is set to"true" - Call that helper method (using
h.current_user_is_admin?) in your decorator.- When the user is an admin, show them the values specified by
ADMIN_ATTRIBUTES - When the user is not an admin, show them only the values specified by
PUBLIC_ATTRIBUTES
- When the user is an admin, show them the values specified by
If you want to play more with marshalling, what would it be like to create descendents of your ArticleDecorator like ArticleDecoratorXML and ArticleDecoratorJSON? What functionality could you add which would allow the user to stay in the "duck typing" mindset, calling the same method on an instance of any of the three decorators but getting back HTML, XML, or JSON?