Implementing HATEOAS with Presenters
I'm a big fan of using the presenter pattern to help separate logic from presentation. There's a great gem named Draper that can help facilitate this pattern in your Rails apps. When doing research for my book about REST, I realized that the presenter pattern is a great way to create responses that comply with the hypermedia constraint, a.k.a. HATEOAS. I wanted to share with you a little bit about how to do this.
Please note that 'REST is over'.
Note: We'll be creating HTML5 responses in this example, as HTML is a hypermedia format, and is therefore conducive to HATEOAS. JSON and XML don't cut it.
First, some setup
I fired up a brand new Rails app by doing this:
$ rails new hateoas_example $ cd hateoas_example $ cat >> Gemfile gem "draper" ^D $ bundle $ rails g resource post title:string body:text $ rake db:migrate $ rails g draper:decorator Post
Okay, now we should be all set up. We've got a Rails app, it's got draper in the Gemfile, we have a Post resource, and our PostDecorator.
I like to do the view first, to drive our what we need elsewhere. Here it is:
<h2>Title</h2> <p><%= @post.title %></p> <h2>Body</h2> <p><%= @post.body %></p> <h2>Links</h2> <ul> <% @post.links.each do |link| %> <li><%= link_to link.text, link.href, :rel => link.rel %></li> <% end %> </ul>
We're displaying our title and body, but we also want to spit out some links. These links should have a few attributes we need. I might even (shhhhhhh) extract this link thing out into a helper to add the rel stuff every time. It just depends. For this example, I didn't feel like it.
Well, we know we're gonna need a
@post variable set, so let's get that going
in our controller:
class PostsController < ApplicationController def show @post = PostDecorator.find(params[:id]) end end
Super simple. Yay Draper!
We know we need a
links method that returns some links, and those links need to
have rel, href, and text attributes. No problem!
class Link < Struct.new(:rel, :href, :text) end class PostDecorator < ApplicationDecorator decorates :post def links [self_link, all_posts_link] end def all_posts_link Link.new("index", h.posts_url, "All posts") end def self_link Link.new("self", h.post_url(post), "This post") end end
Now, we could have just returned an array of three-element arrays, but I really like to use the Struct.new trick to give us an actual class. It makes error messages quite a bit better, and reminds us that we don't happen to have an array, we have a Link.
We construct those links by taking advantage of the 'index' and 'self' rel attributes that are defined in the registry.
That gives us this:
You'll probably want to make a layout that ignores all of the JS stuff, but for this example, I just left it as-is. It's just that easy. Happy linking!