roar 1.0.0 → 1.0.1
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/Gemfile +2 -2
- data/README.markdown +27 -4
- data/examples/example.rb +49 -55
- data/gemfiles/Gemfile.representable-2.1 +1 -1
- data/lib/roar/client.rb +3 -1
- data/lib/roar/json/json_api.rb +3 -3
- data/lib/roar/version.rb +1 -1
- data/roar.gemspec +2 -2
- data/test/decorator_client_test.rb +64 -0
- data/test/json_api_test.rb +310 -295
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9ce6093e4d27114a605b64621c20ab9f5e156266
|
4
|
+
data.tar.gz: ac05b9e8989952f88b86caaf2d344a2467f48968
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 334f83c90798c648945a3d1f0077e503a26f6a51bd2499a3878f48bdb845ad3815a55f8e372078ac28cf5fba628043e7b4edb031625e1abf14ff36dc4fee890d
|
7
|
+
data.tar.gz: 329f87d78af5d6e8f214e5dc71a22ff9ce932967bc8bb0e86f5b45ecad6a21eabf546afecd0690ab05dc8e5ccbcfe5f98cfccddc2ddd18f381e6a7ddaf2e441e
|
data/CHANGES.markdown
CHANGED
data/Gemfile
CHANGED
@@ -3,8 +3,8 @@ source "http://rubygems.org"
|
|
3
3
|
# Specify your gem's dependencies in roar.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
|
-
gem "representable", "~> 2.1.0"
|
7
|
-
|
6
|
+
# gem "representable", "~> 2.1.0"
|
7
|
+
gem "representable", :path => "../representable"
|
8
8
|
|
9
9
|
# as long as this is not merged, i'll vendor the runner file.
|
10
10
|
# gem "sinatra-contrib", :git => "git@github.com:apotonick/sinatra-contrib.git", :branch => "runner"
|
data/README.markdown
CHANGED
@@ -8,10 +8,16 @@ Roar is a framework for parsing and rendering REST documents. Nothing more.
|
|
8
8
|
|
9
9
|
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.
|
10
10
|
|
11
|
-
Roar comes with built-in JSON, JSON-HAL, JSON-API and XML support. Its highly
|
11
|
+
Roar comes with built-in JSON, JSON-HAL, JSON-API and XML support. Its highly modular architecture provides features like coercion, hypermedia, HTTP transport, client caching and more.
|
12
12
|
|
13
13
|
Roar is completely framework-agnostic and loves being used in web kits like Rails, Webmachine, Sinatra, Padrino, etc. If you use Rails, consider [roar-rails](https://github.com/apotonick/roar-rails) for an enjoyable integration.
|
14
14
|
|
15
|
+
<a href="https://leanpub.com/trailblazer">
|
16
|
+
![](https://raw.githubusercontent.com/apotonick/trailblazer/master/doc/trb.jpg)
|
17
|
+
</a>
|
18
|
+
|
19
|
+
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.
|
20
|
+
|
15
21
|
## Representable
|
16
22
|
|
17
23
|
Roar is just a thin layer on top of the [representable](https://github.com/apotonick/representable) gem. While Roar gives you a DSL and behaviour for creating hypermedia APIs, representable implements all the mapping functionality.
|
@@ -235,7 +241,7 @@ We're currently [working on](https://github.com/apotonick/roar/issues/85) better
|
|
235
241
|
Roar provides coercion with the [virtus](https://github.com/solnic/virtus) gem.
|
236
242
|
|
237
243
|
```ruby
|
238
|
-
require 'roar/
|
244
|
+
require 'roar/coercion'
|
239
245
|
|
240
246
|
module SongRepresenter
|
241
247
|
include Roar::JSON
|
@@ -280,7 +286,20 @@ module SongRepresenter
|
|
280
286
|
end
|
281
287
|
```
|
282
288
|
|
283
|
-
The `Hypermedia` feature allows declaring links using the `::link` method.
|
289
|
+
The `Hypermedia` feature allows declaring links using the `::link` method. In the block, you have access to the represented model. When using representer modules, the block is executed in the model's context.
|
290
|
+
|
291
|
+
However, when using decorators, the context is the decorator instance, allowing you to access additional data. Use `represented` to retrieve model data.
|
292
|
+
|
293
|
+
```ruby
|
294
|
+
class SongRepresenter < Roar::Decorator
|
295
|
+
# ..
|
296
|
+
link :self do
|
297
|
+
"http://songs/#{represented.title}"
|
298
|
+
end
|
299
|
+
end
|
300
|
+
```
|
301
|
+
|
302
|
+
This will render links into your representation.
|
284
303
|
|
285
304
|
```ruby
|
286
305
|
song.extend(SongRepresenter)
|
@@ -358,6 +377,8 @@ end
|
|
358
377
|
|
359
378
|
Documentation for HAL can be found in the [API docs](http://rdoc.info/github/apotonick/roar/Roar/Representer/JSON/HAL).
|
360
379
|
|
380
|
+
Make sure you [understand the different contexts](#hypermedia) for links when using decorators.
|
381
|
+
|
361
382
|
### Hypermedia
|
362
383
|
|
363
384
|
Including the `Roar::JSON::HAL` module adds some more DSL methods to your module. It still allows using `::link` but treats them slightly different.
|
@@ -404,6 +425,8 @@ All HAL features in Roar are discussed in the [API docs](http://rdoc.info/github
|
|
404
425
|
|
405
426
|
Roar also supports [JSON-API](http://jsonapi.org/) - yay! It can render _and_ parse singular and collection documents.
|
406
427
|
|
428
|
+
Note that you need representable >= 2.1.4 in your `Gemfile`.
|
429
|
+
|
407
430
|
### Resource
|
408
431
|
|
409
432
|
A minimal representation can be defined as follows.
|
@@ -412,7 +435,7 @@ A minimal representation can be defined as follows.
|
|
412
435
|
require 'roar/json/json_api'
|
413
436
|
|
414
437
|
module SongsRepresenter
|
415
|
-
include Roar::JSON::
|
438
|
+
include Roar::JSON::JSONAPI
|
416
439
|
type :songs
|
417
440
|
|
418
441
|
property :id
|
data/examples/example.rb
CHANGED
@@ -4,7 +4,7 @@ require 'bundler'
|
|
4
4
|
Bundler.setup
|
5
5
|
|
6
6
|
require 'ostruct'
|
7
|
-
require 'roar/
|
7
|
+
require 'roar/json'
|
8
8
|
|
9
9
|
def reset_representer(*module_name)
|
10
10
|
module_name.each do |mod|
|
@@ -19,12 +19,12 @@ class Song < OpenStruct
|
|
19
19
|
end
|
20
20
|
|
21
21
|
module SongRepresenter
|
22
|
-
include Roar::
|
22
|
+
include Roar::JSON
|
23
23
|
|
24
24
|
property :title
|
25
25
|
end
|
26
26
|
|
27
|
-
song = Song.new(title:
|
27
|
+
song = Song.new(title: 'Fate').extend(SongRepresenter)
|
28
28
|
puts song.to_json
|
29
29
|
|
30
30
|
# Parsing
|
@@ -40,13 +40,13 @@ require 'roar/decorator'
|
|
40
40
|
|
41
41
|
module Decorator
|
42
42
|
class SongRepresenter < Roar::Decorator
|
43
|
-
include Roar::
|
43
|
+
include Roar::JSON
|
44
44
|
|
45
45
|
property :title
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
song = Song.new(title:
|
49
|
+
song = Song.new(title: 'Medicine Balls')
|
50
50
|
puts Decorator::SongRepresenter.new(song).to_json
|
51
51
|
|
52
52
|
# Collections
|
@@ -54,14 +54,14 @@ puts Decorator::SongRepresenter.new(song).to_json
|
|
54
54
|
reset_representer(SongRepresenter)
|
55
55
|
|
56
56
|
module SongRepresenter
|
57
|
-
include Roar::
|
57
|
+
include Roar::JSON
|
58
58
|
|
59
59
|
property :title
|
60
60
|
collection :composers
|
61
61
|
end
|
62
62
|
|
63
63
|
|
64
|
-
song = Song.new(title:
|
64
|
+
song = Song.new(title: 'Roxanne', composers: ['Sting', 'Stu Copeland'])
|
65
65
|
song.extend(SongRepresenter)
|
66
66
|
puts song.to_json
|
67
67
|
|
@@ -71,13 +71,13 @@ class Album < OpenStruct
|
|
71
71
|
end
|
72
72
|
|
73
73
|
module AlbumRepresenter
|
74
|
-
include Roar::
|
74
|
+
include Roar::JSON
|
75
75
|
|
76
76
|
property :title
|
77
77
|
collection :songs, extend: SongRepresenter, class: Song
|
78
78
|
end
|
79
79
|
|
80
|
-
album = Album.new(title:
|
80
|
+
album = Album.new(title: 'True North', songs: [Song.new(title: 'The Island'), Song.new(:title => 'Changing Tide')])
|
81
81
|
album.extend(AlbumRepresenter)
|
82
82
|
puts album.to_json
|
83
83
|
|
@@ -88,12 +88,10 @@ album.from_json('{"title":"Indestructible","songs":[{"title":"Tropical London"},
|
|
88
88
|
|
89
89
|
puts album.songs.last.inspect
|
90
90
|
|
91
|
-
# Inline Representers # FIXME: what about collections?
|
92
|
-
|
93
91
|
reset_representer(AlbumRepresenter)
|
94
92
|
|
95
93
|
module AlbumRepresenter
|
96
|
-
include Roar::
|
94
|
+
include Roar::JSON
|
97
95
|
|
98
96
|
property :title
|
99
97
|
|
@@ -102,12 +100,10 @@ module AlbumRepresenter
|
|
102
100
|
end
|
103
101
|
end
|
104
102
|
|
105
|
-
album = Album.new(title:
|
103
|
+
album = Album.new(title: 'True North', songs: [Song.new(title: 'The Island'), Song.new(:title => 'Changing Tide')])
|
106
104
|
album.extend(AlbumRepresenter)
|
107
105
|
puts album.to_json
|
108
106
|
|
109
|
-
|
110
|
-
|
111
107
|
album = Album.new
|
112
108
|
album.extend(AlbumRepresenter)
|
113
109
|
album.from_json('{"title":"True North","songs":[{"title":"The Island"},{"title":"Changing Tide"}]}')
|
@@ -119,7 +115,7 @@ puts album.songs.first.title
|
|
119
115
|
reset_representer(AlbumRepresenter)
|
120
116
|
|
121
117
|
module AlbumRepresenter
|
122
|
-
include Roar::
|
118
|
+
include Roar::JSON
|
123
119
|
|
124
120
|
property :title
|
125
121
|
|
@@ -127,13 +123,13 @@ module AlbumRepresenter
|
|
127
123
|
end
|
128
124
|
|
129
125
|
|
130
|
-
album = Album.new(title:
|
126
|
+
album = Album.new(title: 'True North', songs: [Song.new(title: 'The Island'), Song.new(:title => 'Changing Tide')])
|
131
127
|
album.extend(AlbumRepresenter)
|
132
128
|
|
133
129
|
puts album.songs[0].object_id
|
134
130
|
album.from_json('{"title":"True North","songs":[{"title":"Secret Society"},{"title":"Changing Tide"}]}')
|
135
131
|
puts album.songs[0].title
|
136
|
-
puts album.songs[0].object_id
|
132
|
+
puts album.songs[0].object_id
|
137
133
|
|
138
134
|
# Coercion, renaming, ..
|
139
135
|
|
@@ -142,8 +138,8 @@ puts album.songs[0].object_id##
|
|
142
138
|
reset_representer(SongRepresenter)
|
143
139
|
|
144
140
|
module SongRepresenter
|
145
|
-
include Roar::
|
146
|
-
include Roar::
|
141
|
+
include Roar::JSON
|
142
|
+
include Roar::Hypermedia
|
147
143
|
|
148
144
|
property :title
|
149
145
|
|
@@ -163,7 +159,7 @@ puts song.to_json
|
|
163
159
|
reset_representer(SongRepresenter)
|
164
160
|
|
165
161
|
module SongRepresenter
|
166
|
-
include Roar::
|
162
|
+
include Roar::JSON
|
167
163
|
|
168
164
|
property :title
|
169
165
|
|
@@ -173,7 +169,7 @@ module SongRepresenter
|
|
173
169
|
end
|
174
170
|
|
175
171
|
song.extend(SongRepresenter)
|
176
|
-
puts song.to_json(base_url:
|
172
|
+
puts song.to_json(base_url: 'localhost:3001/')
|
177
173
|
|
178
174
|
|
179
175
|
# Discovering Hypermedia
|
@@ -184,11 +180,11 @@ puts song.links[:self].href
|
|
184
180
|
|
185
181
|
# Media Formats: HAL
|
186
182
|
|
187
|
-
require 'roar/
|
183
|
+
require 'roar/json/hal'
|
188
184
|
|
189
185
|
module HAL
|
190
186
|
module SongRepresenter
|
191
|
-
include Roar::
|
187
|
+
include Roar::JSON::HAL
|
192
188
|
|
193
189
|
property :title
|
194
190
|
|
@@ -204,7 +200,7 @@ puts song.to_json
|
|
204
200
|
reset_representer(AlbumRepresenter)
|
205
201
|
|
206
202
|
module AlbumRepresenter
|
207
|
-
include Roar::
|
203
|
+
include Roar::JSON::HAL
|
208
204
|
|
209
205
|
property :title
|
210
206
|
|
@@ -213,57 +209,55 @@ module AlbumRepresenter
|
|
213
209
|
end
|
214
210
|
end
|
215
211
|
|
216
|
-
album = Album.new(title:
|
212
|
+
album = Album.new(title: 'True North', songs: [Song.new(title: 'The Island'), Song.new(:title => 'Changing Tide')])
|
217
213
|
album.extend(AlbumRepresenter)
|
218
214
|
puts album.to_json
|
219
215
|
|
220
216
|
# Media Formats: JSON+Collection
|
221
217
|
|
222
|
-
require 'roar/
|
218
|
+
require 'roar/json/collection_json'
|
223
219
|
|
224
220
|
|
225
221
|
module Collection
|
226
222
|
module SongRepresenter
|
227
|
-
include Roar::
|
228
|
-
version
|
229
|
-
href {
|
223
|
+
include Roar::JSON::CollectionJSON
|
224
|
+
version '1.0'
|
225
|
+
href { 'http://localhost/songs/' }
|
230
226
|
|
231
227
|
property :title
|
232
228
|
|
233
229
|
items(:class => Song) do
|
234
230
|
href { "//songs/#{title}" }
|
235
231
|
|
236
|
-
property :title, :prompt =>
|
232
|
+
property :title, :prompt => 'Song title'
|
237
233
|
|
238
234
|
link(:download) { "//songs/#{title}.mp3" }
|
239
235
|
end
|
240
236
|
|
241
237
|
template do
|
242
|
-
property :title, :prompt =>
|
238
|
+
property :title, :prompt => 'Song title'
|
243
239
|
end
|
244
240
|
|
245
241
|
queries do
|
246
242
|
link :search do
|
247
|
-
{:href =>
|
243
|
+
{:href => '//search', :data => [{:name => 'q', :value => ''}]}
|
248
244
|
end
|
249
245
|
end
|
250
246
|
end
|
251
247
|
end
|
252
248
|
|
253
|
-
song = Song.new(title:
|
249
|
+
song = Song.new(title: 'Roxanne')
|
254
250
|
song.extend(Collection::SongRepresenter)
|
255
251
|
puts song.to_json
|
256
252
|
|
257
|
-
|
258
|
-
|
259
253
|
# Client-side
|
260
254
|
# share in gem, parse existing document.
|
261
255
|
|
262
256
|
reset_representer(SongRepresenter)
|
263
257
|
|
264
258
|
module SongRepresenter
|
265
|
-
include Roar::
|
266
|
-
include Roar::
|
259
|
+
include Roar::JSON
|
260
|
+
include Roar::Hypermedia
|
267
261
|
|
268
262
|
property :title
|
269
263
|
property :id
|
@@ -274,34 +268,34 @@ module SongRepresenter
|
|
274
268
|
end
|
275
269
|
|
276
270
|
|
277
|
-
require 'roar/
|
271
|
+
require 'roar/client'
|
278
272
|
|
279
273
|
module Client
|
280
274
|
class Song < OpenStruct
|
281
|
-
include Roar::
|
275
|
+
include Roar::JSON
|
282
276
|
include SongRepresenter
|
283
|
-
include Roar::
|
277
|
+
include Roar::Client
|
284
278
|
end
|
285
279
|
end
|
286
280
|
|
287
|
-
song = Client::Song.new(title:
|
288
|
-
song.post(
|
281
|
+
song = Client::Song.new(title: 'Roxanne')
|
282
|
+
song.post(uri: 'http://localhost:4567/songs', as: 'application/json')
|
289
283
|
puts song.id
|
290
284
|
|
291
285
|
|
292
286
|
song = Client::Song.new
|
293
|
-
song.get(
|
287
|
+
song.get(uri: 'http://localhost:4567/songs/1', as: 'application/json')
|
294
288
|
puts song.title
|
295
289
|
puts song.links[:self].href
|
296
290
|
|
297
291
|
# XML
|
298
292
|
|
299
|
-
require 'roar/
|
293
|
+
require 'roar/xml'
|
300
294
|
|
301
295
|
module XML
|
302
296
|
module SongRepresenter
|
303
|
-
include Roar::
|
304
|
-
include Roar::
|
297
|
+
include Roar::XML
|
298
|
+
include Roar::Hypermedia
|
305
299
|
|
306
300
|
property :title
|
307
301
|
property :id
|
@@ -312,7 +306,7 @@ module XML
|
|
312
306
|
end
|
313
307
|
end
|
314
308
|
|
315
|
-
song = Song.new(title:
|
309
|
+
song = Song.new(title: 'Roxanne', id: 42)
|
316
310
|
song.extend(XML::SongRepresenter)
|
317
311
|
puts song.to_xml
|
318
312
|
|
@@ -320,11 +314,11 @@ puts song.to_xml
|
|
320
314
|
|
321
315
|
reset_representer(SongRepresenter)
|
322
316
|
|
323
|
-
require 'roar/
|
317
|
+
require 'roar/coercion'
|
324
318
|
|
325
319
|
module SongRepresenter
|
326
|
-
include Roar::
|
327
|
-
include Roar::
|
320
|
+
include Roar::JSON
|
321
|
+
include Roar::Coercion
|
328
322
|
|
329
323
|
property :title
|
330
324
|
property :released_at, type: DateTime
|
@@ -342,7 +336,7 @@ class LinkOptionsCollection < Array
|
|
342
336
|
end
|
343
337
|
|
344
338
|
module HyperlinkiRepresenter
|
345
|
-
include Roar::
|
339
|
+
include Roar::JSON
|
346
340
|
|
347
341
|
def to_hash(*) # setup the link
|
348
342
|
# FIXME: why does self.to_s throw a stack level too deep (SystemStackError) ?
|
@@ -352,7 +346,7 @@ module HyperlinkiRepresenter
|
|
352
346
|
end
|
353
347
|
|
354
348
|
module Representer
|
355
|
-
include Roar::
|
349
|
+
include Roar::JSON
|
356
350
|
|
357
351
|
def self.links
|
358
352
|
[:self, :next]
|
@@ -363,8 +357,8 @@ module Representer
|
|
363
357
|
def links
|
364
358
|
# get link configurations from representable_attrs object.
|
365
359
|
#self.representable_attrs.links
|
366
|
-
LinkOptionsCollection.new([
|
360
|
+
LinkOptionsCollection.new(['self', 'next'])
|
367
361
|
end
|
368
362
|
end
|
369
363
|
|
370
|
-
puts
|
364
|
+
puts ''.extend(Representer).to_json
|
data/lib/roar/client.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
require "roar/http_verbs"
|
2
2
|
|
3
3
|
module Roar
|
4
|
-
|
4
|
+
|
5
|
+
# Mix in HttpVerbs.
|
5
6
|
module Client
|
6
7
|
include HttpVerbs
|
7
8
|
|
9
|
+
# Add accessors for properties and collections to modules.
|
8
10
|
def self.extended(base)
|
9
11
|
base.instance_eval do
|
10
12
|
representable_attrs.each do |attr|
|