Skip to content
Newer
Older
100644 154 lines (117 sloc) 4.15 KB
1ab921b @steveklabnik adding 'implementing hateoas with presenters'
authored
1 ---
2 layout: post
3 title: "Implementing HATEOAS with Presenters"
4 date: 2011-12-30 11:00
5 ---
6
7 I'm a big fan of using the presenter pattern to help separate logic from
8 presentation. There's a great gem named
9 [Draper](https://github.com/jcasimir/draper) that can help facilitate this
10 pattern in your Rails apps. When doing research for
f8f6618 @steveklabnik getsomere.st -> designinghypermediaapis.com
authored
11 [my book about REST](http://designinghypermediaapis.com/), I realized that the presenter
1ab921b @steveklabnik adding 'implementing hateoas with presenters'
authored
12 pattern is a great way to create responses that comply with the hypermedia
13 constraint, a.k.a. HATEOAS. I wanted to share with you a little bit about
14 how to do this.
15
27d2888 @steveklabnik No seriously, REST is over.
authored
16 Please note that '[REST is over'](/posts/2012-02-23-rest-is-over).
17
1ab921b @steveklabnik adding 'implementing hateoas with presenters'
authored
18 Note: We'll be creating HTML5 responses in this example, as HTML is a hypermedia
19 format, and is therefore conducive to HATEOAS. JSON and XML don't cut it.
20
21 ## First, some setup
22
23 I fired up a brand new Rails app by doing this:
24
25 ```
26 $ rails new hateoas_example
27 $ cd hateoas_example
15723e7 @steveklabnik Come on, _nobody_ realized this typo?
authored
28 $ cat >> Gemfile
1ab921b @steveklabnik adding 'implementing hateoas with presenters'
authored
29 gem "draper"
30 ^D
31 $ bundle
32 $ rails g resource post title:string body:text
33 $ rake db:migrate
34 $ rails g draper:decorator Post
35 ```
36
37 Okay, now we should be all set up. We've got a Rails app, it's got draper
38 in the Gemfile, we have a Post resource, and our PostDecorator.
39
40 ## The View
41
42 I like to do the view first, to drive our what we need elsewhere. Here
43 it is:
44
45 ```
46 <h2>Title</h2>
47 <p><%= @post.title %></p>
48
49 <h2>Body</h2>
50 <p><%= @post.body %></p>
51
52 <h2>Links</h2>
53 <ul>
54 <% @post.links.each do |link| %>
55 <li><%= link_to link.text, link.href, :rel => link.rel %></li>
56 <% end %>
57 </ul>
58 ```
59
60 We're displaying our title and body, but we also want to spit out some links.
61 These links should have a few attributes we need. I might even (shhhhhhh)
62 <small>extract this link thing out into a helper to add the rel stuff every
63 time</small>. It just depends. For this example, I didn't feel like it.
64
65 ## The Controller
66
67 Well, we know we're gonna need a `@post` variable set, so let's get that going
68 in our controller:
69
70 ``` ruby
71 class PostsController < ApplicationController
72 def show
73 @post = PostDecorator.find(params[:id])
74 end
75 end
76 ```
77
78 Super simple. Yay Draper!
79
80 ## The Presenter
81
82 We know we need a `links` method that returns some links, and those links need to
83 have rel, href, and text attributes. No problem!
84
85 ``` ruby
86 class Link < Struct.new(:rel, :href, :text)
87 end
88
89 class PostDecorator < ApplicationDecorator
90 decorates :post
91
92 def links
93 [self_link, all_posts_link]
94 end
95
96 def all_posts_link
97 Link.new("index", h.posts_url, "All posts")
98 end
99
100 def self_link
101 Link.new("self", h.post_url(post), "This post")
102 end
103 end
104 ```
105
106 Now, we could have just returned an array of three-element arrays, but
107 I really like to use the Struct.new trick to give us an actual class.
108 It makes error messages quite a bit better, and reminds us that we
109 don't happen to have an array, we have a Link.
110
111 We construct those links by taking advantage of the 'index' and 'self' rel
112 attributes that are [defined in the registry](http://www.iana.org/assignments/link-relations/link-relations.xml).
113
114 ## The output
115
116 That gives us this:
117
118 ```
119 <!DOCTYPE html>
120 <html>
121 <head>
122 <title>HateoasSample</title>
123 <link href="/assets/application.css?body=1" media="screen" rel="stylesheet" type="text/css" />
124 <link href="/assets/posts.css?body=1" media="screen" rel="stylesheet" type="text/css" />
125 <script src="/assets/jquery.js?body=1" type="text/javascript"></script>
126 <script src="/assets/jquery_ujs.js?body=1" type="text/javascript"></script>
127 <script src="/assets/posts.js?body=1" type="text/javascript"></script>
128 <script src="/assets/application.js?body=1" type="text/javascript"></script>
129 <meta content="authenticity_token" name="csrf-param" />
130 <meta content="0k+SQVv6yr0d12tGWYx7KNXUWaf6f+wgUUNITsAOnHI=" name="csrf-token" />
131 </head>
132 <body>
133
134 <h2>Title</h2>
135 <p>A post, woo hoo!</p>
136
137 <h2>Body</h2>
138 <p>this is some text that's the body of this post</p>
139
140 <h2>Links</h2>
141 <ul>
142 <li><a href="http://localhost:3000/posts/1" rel="self">This post</a></li>
143 <li><a href="http://localhost:3000/posts" rel="index">All posts</a></li>
144 </ul>
145
146
147 </body>
148 </html>
149 ```
150
151 You'll probably want to make a layout that ignores all of the JS stuff, but
152 for this example, I just left it as-is. It's just that easy. Happy linking!
153
Something went wrong with that request. Please try again.