roar 1.0.2 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.github/ISSUE_TEMPLATE.md +20 -0
- data/.travis.yml +16 -11
- data/CHANGES.markdown +86 -57
- data/CONTRIBUTING.md +31 -0
- data/Gemfile +7 -4
- data/LICENSE +1 -1
- data/README.markdown +133 -255
- data/Rakefile +3 -1
- data/examples/example.rb +0 -0
- data/examples/example_server.rb +0 -0
- data/lib/roar/client.rb +8 -3
- data/lib/roar/decorator.rb +2 -2
- data/lib/roar/http_verbs.rb +0 -16
- data/lib/roar/hypermedia.rb +30 -56
- data/lib/roar/json/collection.rb +10 -2
- data/lib/roar/json/hal.rb +74 -83
- data/lib/roar/json.rb +5 -5
- data/lib/roar/version.rb +1 -1
- data/lib/roar/xml.rb +1 -1
- data/lib/roar.rb +3 -3
- data/roar.gemspec +7 -5
- data/test/client_test.rb +1 -1
- data/test/coercion_feature_test.rb +7 -2
- data/test/decorator_test.rb +17 -7
- data/test/hal_json_test.rb +101 -94
- data/test/hypermedia_feature_test.rb +13 -31
- data/test/hypermedia_test.rb +26 -92
- data/test/{decorator_client_test.rb → integration/decorator_client_test.rb} +5 -4
- data/test/{faraday_http_transport_test.rb → integration/faraday_http_transport_test.rb} +1 -0
- data/test/{http_verbs_test.rb → integration/http_verbs_test.rb} +3 -2
- data/test/integration/json_collection_test.rb +35 -0
- data/test/{net_http_transport_test.rb → integration/net_http_transport_test.rb} +1 -0
- data/test/integration/runner.rb +2 -3
- data/test/integration/server.rb +6 -0
- data/test/json_representer_test.rb +2 -29
- data/test/lonely_test.rb +1 -2
- data/test/ssl_client_certs_test.rb +1 -1
- data/test/test_helper.rb +21 -3
- data/test/xml_representer_test.rb +6 -5
- metadata +21 -37
- data/gemfiles/Gemfile.representable-1.7 +0 -6
- data/gemfiles/Gemfile.representable-1.8 +0 -6
- data/gemfiles/Gemfile.representable-2.0 +0 -5
- data/gemfiles/Gemfile.representable-2.1 +0 -5
- data/gemfiles/Gemfile.representable-head +0 -6
- data/lib/roar/json/collection_json.rb +0 -208
- data/lib/roar/json/json_api.rb +0 -233
- data/test/collection_json_test.rb +0 -132
- data/test/hal_links_test.rb +0 -31
- data/test/json_api_test.rb +0 -451
- data/test/lib/runner.rb +0 -134
data/README.markdown
CHANGED
@@ -1,8 +1,47 @@
|
|
1
|
-
#
|
1
|
+
# Roar
|
2
2
|
|
3
3
|
_Resource-Oriented Architectures in Ruby._
|
4
4
|
|
5
|
-
[](https://gitter.im/trailblazer/chat)
|
6
|
+
[](http://trailblazer.to/newsletter/)
|
7
|
+
[](https://travis-ci.org/trailblazer/roar)
|
8
|
+
[](http://badge.fury.io/rb/roar)
|
9
|
+
|
10
|
+
## Table of Contents
|
11
|
+
|
12
|
+
* [Introduction](#introduction)
|
13
|
+
* [Representable](#representable)
|
14
|
+
* [Installation](#installation)
|
15
|
+
* [Dependencies](#dependencies)
|
16
|
+
* [Defining Representers](#defining-representers)
|
17
|
+
* [Rendering](#rendering)
|
18
|
+
* [Parsing](#parsing)
|
19
|
+
* [Module Representers](#module-representers)
|
20
|
+
* [Collections](#collections)
|
21
|
+
* [Nesting](#nesting)
|
22
|
+
* [Inline Representer](#inline-representer)
|
23
|
+
* [Syncing Objects](#syncing-objects)
|
24
|
+
* [Coercion](#coercion)
|
25
|
+
* [More Features](#more-features)
|
26
|
+
* [Hypermedia](#hypermedia)
|
27
|
+
* [Passing Options](#passing-options)
|
28
|
+
* [Specify Decorator](#specify-decorator)
|
29
|
+
* [Consuming Hypermedia](#consuming-hypermedia)
|
30
|
+
* [Media Formats](#media-formats)
|
31
|
+
* [HAL\-JSON](#hal-json)
|
32
|
+
* [Hypermedia](#hypermedia-1)
|
33
|
+
* [Nesting](#nesting-1)
|
34
|
+
* [JSON API](#json-api)
|
35
|
+
* [Client\-Side Support](#client-side-support)
|
36
|
+
* [HTTP Support](#http-support)
|
37
|
+
* [HTTPS](#https)
|
38
|
+
* [Basic Authentication](#basic-authentication)
|
39
|
+
* [Client SSL certificates](#client-ssl-certificates)
|
40
|
+
* [Request customization](#request-customization)
|
41
|
+
* [Error handling](#error-handling)
|
42
|
+
* [XML](#xml)
|
43
|
+
* [Support](#support)
|
44
|
+
* [License](#license)
|
6
45
|
|
7
46
|
## Introduction
|
8
47
|
|
@@ -10,21 +49,15 @@ Roar is a framework for parsing and rendering REST documents. Nothing more.
|
|
10
49
|
|
11
50
|
Representers let you define your API document structure and semantics. They allow both rendering representations from your models _and_ parsing documents to update your Ruby objects. The bi-directional nature of representers make them interesting for both server and client usage.
|
12
51
|
|
13
|
-
Roar comes with built-in JSON, JSON-HAL
|
52
|
+
Roar comes with built-in JSON, JSON-HAL and XML support. JSON API support is available via the [JSON API](https://github.com/trailblazer/roar-jsonapi) gem. Its highly modular architecture provides features like coercion, hypermedia, HTTP transport, client caching and more.
|
14
53
|
|
15
|
-
Roar is completely framework-agnostic and loves being used in web kits like Rails,
|
16
|
-
|
17
|
-
<a href="https://leanpub.com/trailblazer">
|
18
|
-

|
19
|
-
</a>
|
20
|
-
|
21
|
-
Roar is part of the [Trailblazer project](https://github.com/apotonick/trailblazer). Please [buy the book](https://leanpub.com/trailblazer) to support the development. Several chapters will be dedicated to Roar, its integration into operations, hypermedia formats and client-side usage.
|
54
|
+
Roar is completely framework-agnostic and loves being used in web kits like Rails, Hanami, Sinatra, Roda, etc. If you use Rails, consider [roar-rails](https://github.com/apotonick/roar-rails) for an enjoyable integration.
|
22
55
|
|
23
56
|
## Representable
|
24
57
|
|
25
|
-
Roar is just a thin layer on top of the [representable](https://github.com/
|
58
|
+
Roar is just a thin layer on top of the [representable](https://github.com/trailblazer/representable) gem. While Roar gives you a DSL and behaviour for creating hypermedia APIs, representable implements all the mapping functionality.
|
26
59
|
|
27
|
-
If in need for a feature, make sure to check the [representable API docs](https://github.com/
|
60
|
+
If in need for a feature, make sure to check the [representable API docs](https://github.com/trailblazer/representable) first.
|
28
61
|
|
29
62
|
## Installation
|
30
63
|
|
@@ -34,17 +67,23 @@ The roar gem runs with all Ruby versions >= 1.9.3.
|
|
34
67
|
gem 'roar'
|
35
68
|
```
|
36
69
|
|
70
|
+
To use roar with Ruby versions < 2.2.0, add a version pin to your Gemfile:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
gem 'sinatra', '~> 1.4'
|
74
|
+
```
|
75
|
+
|
37
76
|
### Dependencies
|
38
77
|
|
39
78
|
Roar does not bundle dependencies for JSON and XML.
|
40
79
|
|
41
|
-
If you want to use JSON, add the following to your Gemfile
|
80
|
+
If you want to use JSON, add the following to your `Gemfile`:
|
42
81
|
|
43
82
|
```ruby
|
44
83
|
gem 'multi_json'
|
45
84
|
```
|
46
85
|
|
47
|
-
If you want to use XML, add the following to your Gemfile
|
86
|
+
If you want to use XML, add the following to your `Gemfile`:
|
48
87
|
|
49
88
|
```ruby
|
50
89
|
gem 'nokogiri'
|
@@ -56,21 +95,22 @@ gem 'nokogiri'
|
|
56
95
|
Let's see how representers work. They're fun to use.
|
57
96
|
|
58
97
|
```ruby
|
98
|
+
require 'roar/decorator'
|
59
99
|
require 'roar/json'
|
60
100
|
|
61
|
-
|
101
|
+
class SongRepresenter < Roar::Decorator
|
62
102
|
include Roar::JSON
|
63
103
|
|
64
104
|
property :title
|
65
105
|
end
|
66
106
|
```
|
67
107
|
|
68
|
-
API documents are defined using a
|
108
|
+
API documents are defined using a decorator class. You can define plain attributes using the `::property` method.
|
69
109
|
|
70
|
-
Now let's assume we'd have `Song` which is an `ActiveRecord` class. Please note that Roar is not limited to ActiveRecord. In fact, it doesn't really care whether it's representing ActiveRecord,
|
110
|
+
Now let's assume we'd have `Song` which is an `ActiveRecord` class. Please note that Roar is not limited to ActiveRecord. In fact, it doesn't really care whether it's representing ActiveRecord, `Sequel::Model` or just an OpenStruct instance.
|
71
111
|
|
72
112
|
```ruby
|
73
|
-
class Song < ActiveRecord
|
113
|
+
class Song < ActiveRecord::Base
|
74
114
|
end
|
75
115
|
```
|
76
116
|
|
@@ -79,66 +119,62 @@ end
|
|
79
119
|
To render a document, you apply the representer to your model.
|
80
120
|
|
81
121
|
```ruby
|
82
|
-
song = Song.new(title: "
|
83
|
-
song.extend(SongRepresenter)
|
122
|
+
song = Song.new(title: "Medicine Balls")
|
84
123
|
|
85
|
-
song.to_json #=> {"title":"
|
124
|
+
SongRepresenter.new(song).to_json #=> {"title":"Medicine Balls"}
|
86
125
|
```
|
87
126
|
|
88
|
-
Here, the
|
127
|
+
Here, the `song` objects gets wrapped (or "decorated") by the decorator. It is treated as immutable - Roar won't mix in any behaviour.
|
89
128
|
|
90
129
|
## Parsing
|
91
130
|
|
92
131
|
The cool thing about representers is: they can be used for rendering and parsing. See how easy updating your model from a document is.
|
93
132
|
|
94
133
|
```ruby
|
95
|
-
song = Song.new
|
96
|
-
song.extend(SongRepresenter)
|
97
|
-
|
98
|
-
song.from_json('{"title":"Linoleum"}')
|
134
|
+
song = Song.new(title: "Medicine Balls")
|
99
135
|
|
136
|
+
SongRepresenter.new(song).from_json('{"title":"Linoleum"}')
|
100
137
|
song.title #=> Linoleum
|
101
138
|
```
|
102
139
|
|
103
|
-
Again, `#from_json` comes from the representer and just updates the known properties.
|
104
|
-
|
105
140
|
Unknown attributes in the parsed document are simply ignored, making half-baked solutions like `strong_parameters` redundant.
|
106
141
|
|
107
142
|
|
108
|
-
##
|
143
|
+
## Module Representers
|
109
144
|
|
110
|
-
|
145
|
+
**Module Representers are deprecated in Roar 1.1 and will be removed in Roar 2.0.**
|
111
146
|
|
112
|
-
|
113
|
-
require 'roar/decorator'
|
147
|
+
In place of inheriting from `Roar::Decorator`, you can also extend a singleton object with a representer module. Decorators and module representers actually have identical features. You can parse, render, nest, go nuts with both of them.
|
114
148
|
|
115
|
-
class SongRepresenter < Roar::Decorator
|
116
|
-
include Roar::JSON
|
117
|
-
|
118
|
-
property :title
|
119
|
-
end
|
120
|
-
```
|
121
|
-
In place of a module you use a class, the DSL inside is the same you already know.
|
122
149
|
|
123
150
|
```ruby
|
124
|
-
song = Song.new(title: "
|
151
|
+
song = Song.new(title: "Fate")
|
152
|
+
song.extend(SongRepresenter)
|
125
153
|
|
126
|
-
|
154
|
+
song.to_json #=> {"title":"Fate"}
|
127
155
|
```
|
128
156
|
|
129
|
-
Here, the
|
157
|
+
Here, the representer is injected into the actual model and gives us a new `#to_json` method.
|
130
158
|
|
131
|
-
|
159
|
+
This also works both ways.
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
song = Song.new
|
163
|
+
song.extend(SongRepresenter)
|
164
|
+
|
165
|
+
song.from_json('{"title":"Fate"}')
|
166
|
+
song #=> {"title":"Fate"}
|
167
|
+
```
|
132
168
|
|
133
|
-
|
169
|
+
It's worth noting though that many people dislike `#extend` due to well-known performance issues and object pollution. As such this approach is no longer recommended. In this README we'll use decorators to illustrate this library.
|
134
170
|
|
135
171
|
|
136
172
|
## Collections
|
137
173
|
|
138
|
-
Roar (or rather representable) also allows
|
174
|
+
Roar (or rather representable) also allows mapping collections in documents.
|
139
175
|
|
140
176
|
```ruby
|
141
|
-
|
177
|
+
class SongRepresenter < Roar::Decorator
|
142
178
|
include Roar::JSON
|
143
179
|
|
144
180
|
property :title
|
@@ -150,9 +186,8 @@ Where `::property` knows how to handle plain attributes, `::collection` does lis
|
|
150
186
|
|
151
187
|
```ruby
|
152
188
|
song = Song.new(title: "Roxanne", composers: ["Sting", "Stu Copeland"])
|
153
|
-
song.extend(SongRepresenter)
|
154
189
|
|
155
|
-
song.to_json #=> {"title":"Roxanne","composers":["Sting","Stu Copeland"]}
|
190
|
+
SongRepresenter.new(song).to_json #=> {"title":"Roxanne","composers":["Sting","Stu Copeland"]}
|
156
191
|
```
|
157
192
|
|
158
193
|
And, yes, this also works for parsing: `from_json` will create and populate the array of the `composers` attribute.
|
@@ -163,7 +198,7 @@ And, yes, this also works for parsing: `from_json` will create and populate the
|
|
163
198
|
Now what if we need to tackle with collections of `Song`s? We need to implement an `Album` class.
|
164
199
|
|
165
200
|
```ruby
|
166
|
-
class Album < ActiveRecord
|
201
|
+
class Album < ActiveRecord::Base
|
167
202
|
has_many :songs
|
168
203
|
end
|
169
204
|
```
|
@@ -171,7 +206,7 @@ end
|
|
171
206
|
Another representer to represent.
|
172
207
|
|
173
208
|
```ruby
|
174
|
-
|
209
|
+
class AlbumRepresenter < Roar::Decorator
|
175
210
|
include Roar::JSON
|
176
211
|
|
177
212
|
property :title
|
@@ -188,38 +223,35 @@ Consider the following object setup.
|
|
188
223
|
```ruby
|
189
224
|
album = Album.new(title: "True North")
|
190
225
|
album.songs << Song.new(title: "The Island")
|
191
|
-
album.songs << Song.new(:
|
226
|
+
album.songs << Song.new(title: "Changing Tide")
|
192
227
|
```
|
193
228
|
|
194
229
|
You apply the `AlbumRepresenter` and you get a nested document.
|
195
230
|
|
196
231
|
```ruby
|
197
|
-
|
198
|
-
|
199
|
-
album.to_json #=> {"title":"True North","songs":[{"title":"The Island"},{"title":"Changing Tide"}]}
|
232
|
+
AlbumRepresenter.new(album).to_json #=> {"title":"True North","songs":[{"title":"The Island"},{"title":"Changing Tide"}]}
|
200
233
|
```
|
201
234
|
|
202
235
|
This works vice-versa.
|
203
236
|
|
204
237
|
```ruby
|
205
238
|
album = Album.new
|
206
|
-
album.extend(AlbumRepresenter)
|
207
239
|
|
208
|
-
album.from_json('{"title":"Indestructible","songs":[{"title":"Tropical London"},{"title":"Roadblock"}]}')
|
240
|
+
AlbumRepresenter.new(album).from_json('{"title":"Indestructible","songs":[{"title":"Tropical London"},{"title":"Roadblock"}]}')
|
209
241
|
|
210
242
|
puts album.songs[1] #=> #<Song title="Roadblock">
|
211
243
|
```
|
212
244
|
|
213
245
|
The nesting of two representers can map composed object as you find them in many many APIs.
|
214
246
|
|
215
|
-
In case you're after virtual nesting, where a nested block in your document still maps to the same outer object, [check out the `::nested` method](https://github.com/
|
247
|
+
In case you're after virtual nesting, where a nested block in your document still maps to the same outer object, [check out the `::nested` method](https://github.com/trailblazer/representable#document-nesting).
|
216
248
|
|
217
249
|
## Inline Representer
|
218
250
|
|
219
251
|
Sometimes you don't wanna create two separate representers - although it makes them reusable across your app. Use inline representers if you're not intending this.
|
220
252
|
|
221
253
|
```ruby
|
222
|
-
|
254
|
+
class AlbumRepresenter < Roar::Decorator
|
223
255
|
include Roar::JSON
|
224
256
|
|
225
257
|
property :title
|
@@ -238,7 +270,7 @@ This will give you the same rendering and parsing behaviour as in the previous e
|
|
238
270
|
Usually, when parsing, nested objects are created from scratch. If you want nested objects to be updated instead of being newly created, use `parse_strategy:`.
|
239
271
|
|
240
272
|
```ruby
|
241
|
-
|
273
|
+
class AlbumRepresenter < Roar::Decorator
|
242
274
|
include Roar::JSON
|
243
275
|
|
244
276
|
property :title
|
@@ -252,14 +284,14 @@ This will advise Roar to update existing `songs`.
|
|
252
284
|
```ruby
|
253
285
|
album.songs[0].object_id #=> 81431220
|
254
286
|
|
255
|
-
album.from_json('{"title":"True North","songs":[{"title":"Secret Society"},{"title":"Changing Tide"}]}')
|
287
|
+
AlbumRepresenter.new(album).from_json('{"title":"True North","songs":[{"title":"Secret Society"},{"title":"Changing Tide"}]}')
|
256
288
|
|
257
289
|
album.songs[0].title #=> Secret Society
|
258
290
|
album.songs[0].object_id #=> 81431220
|
259
291
|
```
|
260
292
|
Roar didn't create a new `Song` instance but updated its attributes, only.
|
261
293
|
|
262
|
-
We're currently [working on](https://github.com/
|
294
|
+
We're currently [working on](https://github.com/trailblazer/roar/issues/85) better strategies to easily implement `POST` and `PUT` semantics in your APIs without having to worry about the nitty-gritties.
|
263
295
|
|
264
296
|
|
265
297
|
## Coercion
|
@@ -268,8 +300,9 @@ Roar provides coercion with the [virtus](https://github.com/solnic/virtus) gem.
|
|
268
300
|
|
269
301
|
```ruby
|
270
302
|
require 'roar/coercion'
|
303
|
+
require 'roar/json'
|
271
304
|
|
272
|
-
|
305
|
+
class SongRepresenter < Roar::Decorator
|
273
306
|
include Roar::JSON
|
274
307
|
include Roar::Coercion
|
275
308
|
|
@@ -282,9 +315,8 @@ The `:type` option allows to set a virtus-compatible type.
|
|
282
315
|
|
283
316
|
```ruby
|
284
317
|
song = Song.new
|
285
|
-
song.extend(SongRepresenter)
|
286
318
|
|
287
|
-
song.from_json('{"released_at":"1981/03/31"}')
|
319
|
+
SongRepresenter.new(song).from_json('{"released_at":"1981/03/31"}')
|
288
320
|
|
289
321
|
song.released_at #=> 1981-03-31T00:00:00+00:00
|
290
322
|
```
|
@@ -292,7 +324,7 @@ song.released_at #=> 1981-03-31T00:00:00+00:00
|
|
292
324
|
|
293
325
|
## More Features
|
294
326
|
|
295
|
-
Roar/representable gives you many more mapping features like
|
327
|
+
Roar/representable gives you many more mapping features like renaming attributes, wrapping, passing options, etc. See the [representable documentation](http://trailblazer.to/gems/representable/3.0/api.html) for a detailed explanation.
|
296
328
|
|
297
329
|
|
298
330
|
## Hypermedia
|
@@ -300,7 +332,7 @@ Roar/representable gives you many more mapping features like [renaming attribute
|
|
300
332
|
Roar comes with built-in support for embedding and processing hypermedia in your documents.
|
301
333
|
|
302
334
|
```ruby
|
303
|
-
|
335
|
+
class SongRepresenter < Roar::Decorator
|
304
336
|
include Roar::JSON
|
305
337
|
include Roar::Hypermedia
|
306
338
|
|
@@ -328,8 +360,7 @@ end
|
|
328
360
|
This will render links into your representation.
|
329
361
|
|
330
362
|
```ruby
|
331
|
-
|
332
|
-
song.to_json #=> {"title":"Roxanne","links":[{"rel":"self","href":"http://songs/Roxanne"}]}
|
363
|
+
SongRepresenter.new(song).to_json #=> {"title":"Roxanne","links":[{"rel":"self","href":"http://songs/Roxanne"}]}
|
333
364
|
```
|
334
365
|
|
335
366
|
Per default, links are pushed into the hash using the `links` key. Link blocks are executed in represented context, allowing you to call any instance method of your model (here, we call `#title`).
|
@@ -342,7 +373,7 @@ Also, note that [roar-rails](https://github.com/apotonick/roar-rails) allows usi
|
|
342
373
|
Sometimes you need more data in the link block. Data that's not available from the represented model.
|
343
374
|
|
344
375
|
```ruby
|
345
|
-
|
376
|
+
class SongRepresenter < Roar::Decorator
|
346
377
|
include Roar::JSON
|
347
378
|
|
348
379
|
property :title
|
@@ -356,20 +387,36 @@ end
|
|
356
387
|
Pass this data to the rendering method.
|
357
388
|
|
358
389
|
```ruby
|
359
|
-
|
390
|
+
representer = SongRepresenter.new(song)
|
391
|
+
representer.to_json(base_url: "localhost:3001/")
|
360
392
|
```
|
361
393
|
|
362
394
|
Any options passed to `#to_json` will be available as block arguments in the link blocks.
|
363
395
|
|
364
396
|
|
397
|
+
## Specify Decorator
|
398
|
+
|
399
|
+
If you have a property that is a separate class or model, you can specify a decorator for that property. Suppose there is a separate `Artist` model for an album. When the album is eagerly loaded, the artist model could be represented along with it.
|
400
|
+
|
401
|
+
```ruby
|
402
|
+
class ArtistRepresenter < Roar::Decorator
|
403
|
+
property :name
|
404
|
+
end
|
405
|
+
|
406
|
+
class AlbumRepresenter < Roar::Decorator
|
407
|
+
# ..
|
408
|
+
property :artist, decorator: ArtistRepresenter
|
409
|
+
end
|
410
|
+
```
|
411
|
+
|
365
412
|
## Consuming Hypermedia
|
366
413
|
|
367
414
|
Since we defined hypermedia attributes in the representer we can also consume this hypermedia when we parse documents.
|
368
415
|
|
369
416
|
```ruby
|
370
|
-
|
417
|
+
representer.from_json('{"title":"Roxanne","links":[{"rel":"self","href":"http://songs/Roxanne"}]}')
|
371
418
|
|
372
|
-
|
419
|
+
representer.links[:self].href #=> "http://songs/Roxanne"
|
373
420
|
```
|
374
421
|
|
375
422
|
Reading link attributes works by using `#links[]` on the consuming instance.
|
@@ -379,7 +426,7 @@ This allows an easy way to discover hypermedia and build navigational logic on t
|
|
379
426
|
|
380
427
|
## Media Formats
|
381
428
|
|
382
|
-
While Roar comes with a built-in hypermedia format, there's official media types that are widely recognized. Roar currently supports HAL and
|
429
|
+
While Roar comes with a built-in hypermedia format, there's official media types that are widely recognized. Roar currently supports HAL and JSON API.
|
383
430
|
|
384
431
|
Simply by including a module you make your representer understand the media type. This makes it easy to change formats during evaluation.
|
385
432
|
|
@@ -390,7 +437,7 @@ The [HAL](http://stateless.co/hal_specification.html) format is a simple media t
|
|
390
437
|
```ruby
|
391
438
|
require 'roar/json/hal'
|
392
439
|
|
393
|
-
|
440
|
+
class SongRepresenter < Roar::Decorator
|
394
441
|
include Roar::JSON::HAL
|
395
442
|
|
396
443
|
property :title
|
@@ -401,7 +448,7 @@ module SongRepresenter
|
|
401
448
|
end
|
402
449
|
```
|
403
450
|
|
404
|
-
Documentation for HAL can be found in the [API docs](http://rdoc.info/github/
|
451
|
+
Documentation for HAL can be found in the [API docs](http://rdoc.info/github/trailblazer/roar/Roar/JSON/HAL).
|
405
452
|
|
406
453
|
Make sure you [understand the different contexts](#hypermedia) for links when using decorators.
|
407
454
|
|
@@ -410,7 +457,7 @@ Make sure you [understand the different contexts](#hypermedia) for links when us
|
|
410
457
|
Including the `Roar::JSON::HAL` module adds some more DSL methods to your module. It still allows using `::link` but treats them slightly different.
|
411
458
|
|
412
459
|
```ruby
|
413
|
-
|
460
|
+
representer.to_json
|
414
461
|
#=> {"title":"Roxanne","_links":{"self":{"href":"http://songs/Roxanne"}}}
|
415
462
|
```
|
416
463
|
|
@@ -423,7 +470,7 @@ Parsing works like-wise: Roar will use the same setters as before but it knows h
|
|
423
470
|
Nested, or embedded, resources can be defined using the `:embedded` option.
|
424
471
|
|
425
472
|
```ruby
|
426
|
-
|
473
|
+
class AlbumRepresenter < Roar::Decorator
|
427
474
|
include Roar::JSON::HAL
|
428
475
|
|
429
476
|
property :title
|
@@ -437,187 +484,18 @@ end
|
|
437
484
|
To embed a resource, you can use an inline representer or use `:extend` to specify the representer name.
|
438
485
|
|
439
486
|
```ruby
|
440
|
-
album.to_json
|
487
|
+
AlbumRepresenter.new(album).to_json
|
441
488
|
|
442
489
|
#=> {"title":"True North","_embedded":{"songs":[{"title":"The Island"},{"title":"Changing Tide"}]}}
|
443
490
|
```
|
444
491
|
|
445
492
|
HAL keys nested resources under the `_embedded` key and then by their type.
|
446
493
|
|
447
|
-
All HAL features in Roar are discussed in the [API docs](http://rdoc.info/github/
|
494
|
+
All HAL features in Roar are discussed in the [API docs](http://rdoc.info/github/trailblazer/roar/Roar/JSON/HAL), including [array links](https://github.com/trailblazer/roar/blob/master/lib/roar/json/hal.rb#L196).
|
448
495
|
|
496
|
+
## JSON API
|
449
497
|
|
450
|
-
|
451
|
-
|
452
|
-
Roar also supports [JSON-API](http://jsonapi.org/) - yay! It can render _and_ parse singular and collection documents.
|
453
|
-
|
454
|
-
Note that you need representable >= 2.1.4 in your `Gemfile`.
|
455
|
-
|
456
|
-
### Resource
|
457
|
-
|
458
|
-
A minimal representation can be defined as follows.
|
459
|
-
|
460
|
-
```ruby
|
461
|
-
require 'roar/json/json_api'
|
462
|
-
|
463
|
-
module SongsRepresenter
|
464
|
-
include Roar::JSON::JSONAPI
|
465
|
-
type :songs
|
466
|
-
|
467
|
-
property :id
|
468
|
-
property :title
|
469
|
-
end
|
470
|
-
```
|
471
|
-
|
472
|
-
Properties of the represented model are defined in the root level.
|
473
|
-
|
474
|
-
### Hypermedia
|
475
|
-
|
476
|
-
You can add links to `linked` models within the resource section.
|
477
|
-
|
478
|
-
```ruby
|
479
|
-
module SongsRepresenter
|
480
|
-
# ...
|
481
|
-
|
482
|
-
has_one :composer
|
483
|
-
has_many :listeners
|
484
|
-
end
|
485
|
-
```
|
486
|
-
|
487
|
-
Global `links` can be added using the familiar `::link` method (this is still WIP as the DSL is not final).
|
488
|
-
|
489
|
-
```ruby
|
490
|
-
module SongsRepresenter
|
491
|
-
# ...
|
492
|
-
|
493
|
-
link "songs.album" do
|
494
|
-
{
|
495
|
-
type: "album",
|
496
|
-
href: "http://example.com/albums/{songs.album}"
|
497
|
-
}
|
498
|
-
end
|
499
|
-
end
|
500
|
-
```
|
501
|
-
|
502
|
-
### Compounds
|
503
|
-
|
504
|
-
To add compound models into the document, use `::compound`.
|
505
|
-
|
506
|
-
```ruby
|
507
|
-
module SongsRepresenter
|
508
|
-
# ...
|
509
|
-
|
510
|
-
compound do
|
511
|
-
property :album do
|
512
|
-
property :id
|
513
|
-
property :title
|
514
|
-
end
|
515
|
-
|
516
|
-
collection :musicians do
|
517
|
-
property :name
|
518
|
-
end
|
519
|
-
end
|
520
|
-
```
|
521
|
-
|
522
|
-
### Meta Data
|
523
|
-
|
524
|
-
Meta data can be included into the rendered collection document in two ways. Please note that parsing the `meta` field is not implemented, yet, as I wasn't sure if people need it.
|
525
|
-
|
526
|
-
You can define meta data on your collection object and then let Roar compile it.
|
527
|
-
|
528
|
-
```ruby
|
529
|
-
module SongsRepresenter
|
530
|
-
# ..
|
531
|
-
|
532
|
-
meta do
|
533
|
-
property :page
|
534
|
-
property :total
|
535
|
-
end
|
536
|
-
```
|
537
|
-
|
538
|
-
Your collection object has to expose those methods.
|
539
|
-
|
540
|
-
```ruby
|
541
|
-
collection.page #=> 1
|
542
|
-
collection.total #=> 12
|
543
|
-
```
|
544
|
-
|
545
|
-
This will render the `{"meta": {"page": 1, "total": 12}}` hash into the JSON-API document.
|
546
|
-
|
547
|
-
Another way is to provide the _complete_ meta data hash when rendering. You must not define any `meta` properties in the representer when using this approach.
|
548
|
-
|
549
|
-
```ruby
|
550
|
-
collection.to_json("meta" => {page: params["page"], total: collection.size})
|
551
|
-
```
|
552
|
-
|
553
|
-
If you need more functionality (and parsing), please let us know.
|
554
|
-
|
555
|
-
### Usage
|
556
|
-
|
557
|
-
As JSON-API per definition can represent singular models and collections you have two entry points.
|
558
|
-
|
559
|
-
```ruby
|
560
|
-
SongsRepresenter.prepare(Song.find(1)).to_json
|
561
|
-
SongsRepresenter.prepare(Song.new).from_json("..")
|
562
|
-
```
|
563
|
-
|
564
|
-
Singular models can use the representer module directly.
|
565
|
-
|
566
|
-
```ruby
|
567
|
-
SongsRepresenter.for_collection.prepare([Song.find(1), Song.find(2)]).to_json
|
568
|
-
SongsRepresenter.for_collection.prepare([Song.new, Song.new]).from_json("..")
|
569
|
-
```
|
570
|
-
|
571
|
-
|
572
|
-
Parsing currently works great with singular documents - for collections, we are still working out how to encode the application semantics. Feel free to help.
|
573
|
-
|
574
|
-
|
575
|
-
## Collection+JSON
|
576
|
-
|
577
|
-
The [Collection+JSON media format](http://amundsen.com/media-types/collection/) defines document format and semantics for requests. It is currently experimental as we're still exploring how we optimize the support with Roar. Let us know if you're using it.
|
578
|
-
|
579
|
-
```ruby
|
580
|
-
module SongRepresenter
|
581
|
-
include Roar::JSON::CollectionJSON
|
582
|
-
version "1.0"
|
583
|
-
href { "http://localhost/songs/" }
|
584
|
-
|
585
|
-
property :title
|
586
|
-
|
587
|
-
items(:class => Song) do
|
588
|
-
href { "//songs/#{title}" }
|
589
|
-
|
590
|
-
property :title, :prompt => "Song title"
|
591
|
-
|
592
|
-
link(:download) { "//songs/#{title}.mp3" }
|
593
|
-
end
|
594
|
-
|
595
|
-
template do
|
596
|
-
property :title, :prompt => "Song title"
|
597
|
-
end
|
598
|
-
|
599
|
-
queries do
|
600
|
-
link :search do
|
601
|
-
{:href => "//search", :data => [{:name => "q", :value => ""}]}
|
602
|
-
end
|
603
|
-
end
|
604
|
-
end
|
605
|
-
```
|
606
|
-
|
607
|
-
It renders a document following the Collection+JSON specs.
|
608
|
-
|
609
|
-
```
|
610
|
-
#=> {"collection":{
|
611
|
-
"template":{"data":[{"name":"title","value":null}]},
|
612
|
-
"queries":[{"rel":"search","href":"//search","data":[{"name":"q","value":""}]}],
|
613
|
-
"version":"1.0",
|
614
|
-
"href":"http://localhost/songs/",
|
615
|
-
"title":"Roxanne",
|
616
|
-
"items":null}}
|
617
|
-
```
|
618
|
-
|
619
|
-
We have big plans with this media format, as the object model in Roar plays nicely with Collection+JSON's API semantics.
|
620
|
-
|
498
|
+
Roar also supports [JSON API](http://jsonapi.org/) via the [Roar JSON API gem](https://github.com/trailblazer/roar-jsonapi).
|
621
499
|
|
622
500
|
## Client-Side Support
|
623
501
|
|
@@ -626,7 +504,7 @@ Being a bi-directional mapper that does rendering _and_ parsing, Roar represente
|
|
626
504
|
Consider the following shared representer.
|
627
505
|
|
628
506
|
```ruby
|
629
|
-
|
507
|
+
class SongRepresenter < Roar::Decorator
|
630
508
|
include Roar::JSON
|
631
509
|
include Roar::Hypermedia
|
632
510
|
|
@@ -643,6 +521,7 @@ In a client where you don't have access to the database it is common to use `Ope
|
|
643
521
|
|
644
522
|
```ruby
|
645
523
|
require 'roar/client'
|
524
|
+
require 'roar/json'
|
646
525
|
|
647
526
|
class Song < OpenStruct
|
648
527
|
include Roar::JSON
|
@@ -738,7 +617,7 @@ rescue Roar::Transport::Error => exception
|
|
738
617
|
Roar also comes with XML support.
|
739
618
|
|
740
619
|
```ruby
|
741
|
-
|
620
|
+
class SongRepresenter < Roar::Decorator
|
742
621
|
include Roar::XML
|
743
622
|
include Roar::Hypermedia
|
744
623
|
|
@@ -755,9 +634,8 @@ Include the `Roar::XML` engine and get bi-directional XML for your objects.
|
|
755
634
|
|
756
635
|
```ruby
|
757
636
|
song = Song.new(title: "Roxanne", id: 42)
|
758
|
-
song.extend(XML::SongRepresenter)
|
759
637
|
|
760
|
-
song.to_xml
|
638
|
+
SongRepresenter.new(song).to_xml
|
761
639
|
```
|
762
640
|
|
763
641
|
Note that you now use `#to_xml` and `#from_xml`.
|
@@ -770,7 +648,7 @@ Note that you now use `#to_xml` and `#from_xml`.
|
|
770
648
|
</song>
|
771
649
|
```
|
772
650
|
|
773
|
-
Please consult the [representable XML documentation](https://github.com/
|
651
|
+
Please consult the [representable XML documentation](https://github.com/trailblazer/representable/#more-on-xml) for all its great features.
|
774
652
|
|
775
653
|
|
776
654
|
## Support
|