roar 0.12.1 → 0.12.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.markdown +4 -0
- data/README.markdown +559 -0
- data/examples/example.rb +368 -0
- data/examples/example_server.rb +18 -0
- data/lib/roar/representer/xml.rb +1 -1
- data/lib/roar/version.rb +1 -1
- data/test/hypermedia_feature_test.rb +57 -16
- data/test/test_helper.rb +4 -0
- metadata +5 -3
- data/README.textile +0 -331
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: roar
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.12.
|
4
|
+
version: 0.12.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-12-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: representable
|
@@ -135,9 +135,11 @@ files:
|
|
135
135
|
- CHANGES.markdown
|
136
136
|
- Gemfile
|
137
137
|
- LICENSE
|
138
|
-
- README.
|
138
|
+
- README.markdown
|
139
139
|
- Rakefile
|
140
140
|
- TODO.markdown
|
141
|
+
- examples/example.rb
|
142
|
+
- examples/example_server.rb
|
141
143
|
- gemfiles/Gemfile.representable-1.5.ruby-1.8
|
142
144
|
- gemfiles/Gemfile.representable-1.5.ruby-1.9
|
143
145
|
- gemfiles/Gemfile.representable-1.6.ruby-1.9
|
data/README.textile
DELETED
@@ -1,331 +0,0 @@
|
|
1
|
-
h1. ROAR
|
2
|
-
|
3
|
-
_Resource-Oriented Architectures in Ruby._
|
4
|
-
|
5
|
-
h2. Introduction
|
6
|
-
|
7
|
-
Roar is a framework for parsing and rendering REST documents. Nothing more.
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
coercion
|
12
|
-
|
13
|
-
|
14
|
-
With Roar, REST documents - also known as representations - are defined using a new concept called representers. Both syntax and semantics are declared in Ruby modules that can be mixed into your domain models, following clean OOP patterns.
|
15
|
-
|
16
|
-
Roar comes with built-in JSON, JSON::HAL and XML support. It exposes a highly modular architecture and makes it very simple to add new media types and functionality where needed. Additional features include client HTTP support, coercion, client-side caching, awesome hypermedia support and more. Representers fit pretty well in DCI environments, too.
|
17
|
-
|
18
|
-
Roar is completely framework-agnostic and loves being used in web kits like Rails, Webmachine, Sinatra, Padrino, etc. Actually, Roar makes it fun designing real, hypermedia-driven, and resource-oriented systems that will even make Steve sleep happily at night so he finally gets some REST!
|
19
|
-
|
20
|
-
Note: This README sucks and will be updated this week (April 10, 2013).
|
21
|
-
|
22
|
-
h2. Example
|
23
|
-
|
24
|
-
Say your webshop consists of two completely separated apps. The REST backend, a Sinatra app, serves articles and processes orders. The frontend, being browsed by your clients, is a rich Rails application. It queries the services for articles, renders them nicely and reads or writes orders with REST calls. That being said, the frontend turns out to be a pure REST client.
|
25
|
-
|
26
|
-
|
27
|
-
h2. Representations
|
28
|
-
|
29
|
-
Representations are the pivotal elements of REST. Work in a REST system means working with representations, which can be put down to parsing or extracting representations and rendering the like.
|
30
|
-
|
31
|
-
Roar makes it easy to render and parse representations of resources after defining the formats.
|
32
|
-
|
33
|
-
|
34
|
-
h3. Creating Representations
|
35
|
-
|
36
|
-
Why not GET a particular article, what about a good beer?
|
37
|
-
|
38
|
-
@GET http://articles/lonestarbeer@
|
39
|
-
|
40
|
-
It's cheap and it's good. The response of a GET is a representation of the requested resource. A *representation* is always a *document*. In this example, it's a bit of JSON.
|
41
|
-
|
42
|
-
pre. { "article": {
|
43
|
-
"title": "Lonestar Beer",
|
44
|
-
"id": 4711,
|
45
|
-
"links":[
|
46
|
-
{ "rel": "self",
|
47
|
-
"href": "http://articles/lonestarbeer"}
|
48
|
-
]}
|
49
|
-
}
|
50
|
-
|
51
|
-
p. In addition to boring article data, there's a _link_ embedded in the document. This is *hypermedia*, yeah! We will learn more about that shortly.
|
52
|
-
|
53
|
-
So, how did the service render that JSON document? It could use an ERB template, @#to_json@, or maybe another gem. The document could also be created by a *representer*.
|
54
|
-
|
55
|
-
Representers are the key ingredience in Roar, so let's check them out!
|
56
|
-
|
57
|
-
|
58
|
-
h2. Representers
|
59
|
-
|
60
|
-
Representers are most usable when defined in a module, and then mixed into a host class. In our example, the host class is the article.
|
61
|
-
|
62
|
-
<pre>
|
63
|
-
class Article
|
64
|
-
attr_accessor :title, :id
|
65
|
-
end
|
66
|
-
</pre>
|
67
|
-
|
68
|
-
To render a representational document from the article, the backend service has to define a representer.
|
69
|
-
|
70
|
-
<pre>
|
71
|
-
require 'roar/representer/json'
|
72
|
-
require 'roar/representer/feature/hypermedia'
|
73
|
-
|
74
|
-
|
75
|
-
module ArticleRepresenter
|
76
|
-
include Roar::Representer::JSON
|
77
|
-
include Roar::Representer::Feature::Hypermedia
|
78
|
-
|
79
|
-
property :title
|
80
|
-
property :id
|
81
|
-
|
82
|
-
link :self do
|
83
|
-
article_url(self)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
</pre>
|
87
|
-
|
88
|
-
Hooray, we can define plain properties and embedd links easily - and we can even use URL helpers (in Rails, using the "roar-rails gem":https://github.com/apotonick/roar-rails). There's even more, nesting, collections, but more on that later!
|
89
|
-
|
90
|
-
|
91
|
-
h3. Rendering Representations in the Service
|
92
|
-
|
93
|
-
In order to *render* an actual document, the backend service would have to do a few steps: creating a representer, filling in data, and then serialize it.
|
94
|
-
|
95
|
-
<pre>loney = Article.new.extend(ArticleRepresenter)
|
96
|
-
loney.title = "Lonestar"
|
97
|
-
loney.id = 666
|
98
|
-
loney.to_json # => "{\"article\":{\"id\":666, ...
|
99
|
-
</pre>
|
100
|
-
|
101
|
-
Articles itself are useless, so they may be placed into orders. This is the next example.
|
102
|
-
|
103
|
-
|
104
|
-
h3. Nesting Representations
|
105
|
-
|
106
|
-
What if we wanted to check an existing order? We'd @GET http://orders/1@, right?
|
107
|
-
|
108
|
-
<pre>{ "order": {
|
109
|
-
"id": 1,
|
110
|
-
"client_id": "815",
|
111
|
-
"articles": [
|
112
|
-
{"title": "Lonestar Beer",
|
113
|
-
"id": 666,
|
114
|
-
"links":[
|
115
|
-
{ "rel": "self",
|
116
|
-
"href": "http://articles/lonestarbeer"}
|
117
|
-
]}
|
118
|
-
],
|
119
|
-
"links":[
|
120
|
-
{ "rel": "self",
|
121
|
-
"href": "http://orders/1"},
|
122
|
-
{ "rel": "items",
|
123
|
-
"href": "http://orders/1/items"}
|
124
|
-
]}
|
125
|
-
}
|
126
|
-
</pre>
|
127
|
-
|
128
|
-
The order model is simple.
|
129
|
-
|
130
|
-
<pre>
|
131
|
-
class Order
|
132
|
-
attr_accessor :id, :client_id, :articles
|
133
|
-
end
|
134
|
-
</pre>
|
135
|
-
|
136
|
-
Since orders may contain a composition of articles, how would the order service define its representer?
|
137
|
-
|
138
|
-
<pre>
|
139
|
-
module OrderRepresenter
|
140
|
-
include Roar::Representer::JSON
|
141
|
-
include Roar::Representer::Feature::Hypermedia
|
142
|
-
|
143
|
-
property :id
|
144
|
-
property :client_id
|
145
|
-
|
146
|
-
collection :articles, :class => Article
|
147
|
-
|
148
|
-
link :self do
|
149
|
-
order_url(represented)
|
150
|
-
end
|
151
|
-
|
152
|
-
link :items do
|
153
|
-
items_url
|
154
|
-
end
|
155
|
-
end
|
156
|
-
</pre>
|
157
|
-
|
158
|
-
Representers don't have to be in modules, but can be
|
159
|
-
|
160
|
-
The declarative @#collection@ method lets us define compositions of representers.
|
161
|
-
|
162
|
-
|
163
|
-
h3. Parsing Documents in the Service
|
164
|
-
|
165
|
-
Rendering stuff is easy: Representers allow defining the layout and serializing documents for us. However, representers can do more. They work _bi-directional_ in terms of rendering outgoing _and_ parsing incoming representation documents.
|
166
|
-
|
167
|
-
If we were to implement an endpoint for creating new orders, we'd allow POST to @http://orders/@. Let's explore the service code for parsing and creation.
|
168
|
-
|
169
|
-
<pre>
|
170
|
-
post "/orders" do
|
171
|
-
order = Order.new.extend(OrderRepresenter)
|
172
|
-
order.from_json(request.body.string)
|
173
|
-
order.to_json
|
174
|
-
end
|
175
|
-
</pre>
|
176
|
-
|
177
|
-
Look how the @#from_json@ method helps extracting data from the incoming document and, again, @#to_json@ returns the freshly created order's representation. Roar's representers are truely working in both directions, rendering and parsing and thus prevent you from redundant knowledge sharing.
|
178
|
-
|
179
|
-
|
180
|
-
h2. Representers in the Client
|
181
|
-
|
182
|
-
The new representer abstraction layer seems complex and irritating first, where you used @params[]@ and @#to_json@ is a new OOP instance now. But... the cool thing is: You can package representers in gems and distribute them to your client layer as well. In our example, the web frontend can take advantage of the representers, too.
|
183
|
-
|
184
|
-
|
185
|
-
h3. Using HTTP
|
186
|
-
|
187
|
-
Communication between REST clients and services happens via HTTP - clients request, services respond. There are plenty of great gems helping out, namely Restfulie, HTTParty, etc. Representers in Roar provide support for HTTP as well, given you mix in the @HTTPVerbs@ feature module!
|
188
|
-
|
189
|
-
To create a new order, the frontend needs to POST to the REST backend. Here's how this could happen using a representer on HTTP.
|
190
|
-
|
191
|
-
|
192
|
-
<pre>
|
193
|
-
order = Order.new(:client_id => current_user.id)
|
194
|
-
order.post("http://orders/")
|
195
|
-
</pre>
|
196
|
-
|
197
|
-
A couple of noteworthy steps happen here.
|
198
|
-
|
199
|
-
# Using the constructor a blank order document is created.
|
200
|
-
# Initial values like the client's id are passed as arguments and placed in the document.
|
201
|
-
# The filled-out document is POSTed to the given URL.
|
202
|
-
# The backend service creates an actual order record and sends back the representation.
|
203
|
-
# In the @#post@ call, the returned document is parsed and attributes in the representer instance are updated accordingly,
|
204
|
-
|
205
|
-
After the HTTP roundtrip, the order instance keeps all the information we need for proceeding the ordering workflow.
|
206
|
-
|
207
|
-
<pre>
|
208
|
-
order.id #=> 42
|
209
|
-
</pre>
|
210
|
-
|
211
|
-
h3. Discovering Hypermedia
|
212
|
-
|
213
|
-
Now that we got a fresh order, let's place some items! The system's API allows adding articles to an existing order by POSTing articles to a specific resource. This endpoint is propagated in the order document using *hypermedia*.
|
214
|
-
|
215
|
-
Where and what is this hypermedia?
|
216
|
-
|
217
|
-
First, check the JSON document we get back from the POST.
|
218
|
-
|
219
|
-
<pre>{ "order": {
|
220
|
-
"id": 42,
|
221
|
-
"client_id": 1337,
|
222
|
-
"articles": [],
|
223
|
-
"links":[
|
224
|
-
{ "rel": "self",
|
225
|
-
"href": "http://orders/42"},
|
226
|
-
{ "rel": "items",
|
227
|
-
"href": "http://orders/42/items"}
|
228
|
-
]}
|
229
|
-
}
|
230
|
-
</pre>
|
231
|
-
|
232
|
-
Two hypermedia links are embedded in this representation, both feature a @rel@ attribute for defining a link semantic - a "meaning" - and a @href@ attribute for a network address. Isn't that great?
|
233
|
-
|
234
|
-
* The @self@ link refers to the actual resource. It's a REST best practice and representations should always refer to their resource address.
|
235
|
-
* The @items@ link is what we want. The address @http://orders/42/items@ is what we have to refer to when adding articles to this order. Why? Cause we decided that!
|
236
|
-
|
237
|
-
|
238
|
-
h3. Using Hypermedia
|
239
|
-
|
240
|
-
Let the frontend add the delicious "Lonestar" beer to our order, now!
|
241
|
-
|
242
|
-
<pre>
|
243
|
-
beer = Article.new(:title => "Lonestar Beer")
|
244
|
-
beer.post(order.links[:items])
|
245
|
-
</pre>
|
246
|
-
|
247
|
-
That's all we need to do.
|
248
|
-
|
249
|
-
# First, we create an appropriate article representation.
|
250
|
-
# Then, the @#links@ method helps extracting the @items@ link URL from the order document.
|
251
|
-
# A simple POST to the respective address places the item in the order.
|
252
|
-
|
253
|
-
The @order@ instance in the frontend is now stale - it doesn't contain articles, yet, since it is still the document from the first post to @http://orders/@.
|
254
|
-
|
255
|
-
<pre>
|
256
|
-
order.items #=> []
|
257
|
-
</pre>
|
258
|
-
|
259
|
-
To update attributes, a GET is needed.
|
260
|
-
|
261
|
-
<pre>
|
262
|
-
order.get!(order.links[:self])
|
263
|
-
</pre>
|
264
|
-
|
265
|
-
Again, we use hypermedia to retrieve the order's URL. And now, the added article is included in the order.
|
266
|
-
|
267
|
-
[*Note:* If this looks clumsy - It's just the raw API for representers. You might be interested in the upcoming DSL that simplifys frequent workflows as updating a representer.]
|
268
|
-
|
269
|
-
<pre>
|
270
|
-
order.to_attributes #=> {:id => 42, :client_id => 1337,
|
271
|
-
:articles => [{:title => "Lonestar Beer", :id => 666}]}
|
272
|
-
</pre>
|
273
|
-
|
274
|
-
This is cool, we used REST representers and hypermedia to create an order and fill it with articles. It's time for a beer, isn't it?
|
275
|
-
|
276
|
-
|
277
|
-
h3. Using Accessors
|
278
|
-
|
279
|
-
What if the ordering API is going a different way? What if we had to place articles into the order document ourselves, and then PUT this representation to @http://orders/42@? No problem with representers!
|
280
|
-
|
281
|
-
Here's what could happen in the frontend.
|
282
|
-
|
283
|
-
<pre>
|
284
|
-
beer = Article.new(:title => "Lonestar Beer")
|
285
|
-
order.items << beer
|
286
|
-
order.post(order.links[:self])
|
287
|
-
</pre>
|
288
|
-
|
289
|
-
This was dead simple since representations can be composed of different documents in Roar.
|
290
|
-
|
291
|
-
h2. Decorators
|
292
|
-
|
293
|
-
You can use `Roar::Decorator` as a representer. More docs coming soon. Note that if you want your represented object to save incoming hypermedia you need to do two things.
|
294
|
-
|
295
|
-
<pre>
|
296
|
-
class SongClient
|
297
|
-
# do whatever here
|
298
|
-
|
299
|
-
attr_accessor :links
|
300
|
-
end
|
301
|
-
|
302
|
-
class SongRepresenter < Roar::Decorator
|
303
|
-
include Roar::Representer::JSON
|
304
|
-
include Roar::Decorator::HypermediaConsumer
|
305
|
-
end
|
306
|
-
</pre>
|
307
|
-
|
308
|
-
|
309
|
-
h2. More Features
|
310
|
-
|
311
|
-
Be sure to check out the bundled features.
|
312
|
-
|
313
|
-
# *Coercion* transforms values to typed objects when parsing a document. Uses virtus.
|
314
|
-
# *Faraday* support, if you want to use it install the Faraday gem and require 'roar/representer/transport/faraday' and configure 'Roar::Representer::Feature::HttpVerbs.transport_engine = Roar::Representer::Transport::Faraday'
|
315
|
-
|
316
|
-
h2. What is REST about?
|
317
|
-
|
318
|
-
Making that system RESTful basically means
|
319
|
-
|
320
|
-
# The frontend knows one _single entry point_ URL to the REST services. This is @http://orders@.
|
321
|
-
# Do _not_ let the frontend compute any URLs to further actions.
|
322
|
-
# Showing articles, creating a new order, adding articles to it and finally placing the order - this all requires further URLs. These URLs are embedded as _hypermedia_ in the representations sent by the REST backend.
|
323
|
-
|
324
|
-
h2. Support
|
325
|
-
|
326
|
-
Questions? Need help? Free 1st Level Support on irc.freenode.org#roar !
|
327
|
-
We also have a "mailing list":https://groups.google.com/forum/?fromgroups#!forum/roar-talk, yiha!
|
328
|
-
|
329
|
-
h2. License
|
330
|
-
|
331
|
-
Roar is released under the "MIT License":http://www.opensource.org/licenses/MIT.
|