roar 0.12.1 → 0.12.2
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 +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/examples/example.rb
ADDED
@@ -0,0 +1,368 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler.setup
|
3
|
+
|
4
|
+
require 'ostruct'
|
5
|
+
require 'roar/representer/json'
|
6
|
+
|
7
|
+
def reset_representer(*module_name)
|
8
|
+
module_name.each do |mod|
|
9
|
+
mod.module_eval do
|
10
|
+
@representable_attrs = nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
class Song < OpenStruct
|
17
|
+
end
|
18
|
+
|
19
|
+
module SongRepresenter
|
20
|
+
include Roar::Representer::JSON
|
21
|
+
|
22
|
+
property :title
|
23
|
+
end
|
24
|
+
|
25
|
+
song = Song.new(title: "Fate").extend(SongRepresenter)
|
26
|
+
puts song.to_json
|
27
|
+
|
28
|
+
# Parsing
|
29
|
+
|
30
|
+
song = Song.new.extend(SongRepresenter)
|
31
|
+
song.from_json('{"title":"Linoleum"}')
|
32
|
+
puts song.title
|
33
|
+
|
34
|
+
|
35
|
+
# Decorator
|
36
|
+
|
37
|
+
require 'roar/decorator'
|
38
|
+
|
39
|
+
module Decorator
|
40
|
+
class SongRepresenter < Roar::Decorator
|
41
|
+
include Roar::Representer::JSON
|
42
|
+
|
43
|
+
property :title
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
song = Song.new(title: "Medicine Balls")
|
48
|
+
puts Decorator::SongRepresenter.new(song).to_json
|
49
|
+
|
50
|
+
# Collections
|
51
|
+
|
52
|
+
reset_representer(SongRepresenter)
|
53
|
+
|
54
|
+
module SongRepresenter
|
55
|
+
include Roar::Representer::JSON
|
56
|
+
|
57
|
+
property :title
|
58
|
+
collection :composers
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
song = Song.new(title: "Roxanne", composers: ["Sting", "Stu Copeland"])
|
63
|
+
song.extend(SongRepresenter)
|
64
|
+
puts song.to_json
|
65
|
+
|
66
|
+
# Nesting
|
67
|
+
|
68
|
+
class Album < OpenStruct
|
69
|
+
end
|
70
|
+
|
71
|
+
module AlbumRepresenter
|
72
|
+
include Roar::Representer::JSON
|
73
|
+
|
74
|
+
property :title
|
75
|
+
collection :songs, extend: SongRepresenter, class: Song
|
76
|
+
end
|
77
|
+
|
78
|
+
album = Album.new(title: "True North", songs: [Song.new(title: "The Island"), Song.new(:title => "Changing Tide")])
|
79
|
+
album.extend(AlbumRepresenter)
|
80
|
+
puts album.to_json
|
81
|
+
|
82
|
+
album = Album.new
|
83
|
+
album.extend(AlbumRepresenter)
|
84
|
+
|
85
|
+
album.from_json('{"title":"Indestructible","songs":[{"title":"Tropical London"},{"title":"Roadblock"}]}')
|
86
|
+
|
87
|
+
puts album.songs.last.inspect
|
88
|
+
|
89
|
+
# Inline Representers # FIXME: what about collections?
|
90
|
+
|
91
|
+
reset_representer(AlbumRepresenter)
|
92
|
+
|
93
|
+
module AlbumRepresenter
|
94
|
+
include Roar::Representer::JSON
|
95
|
+
|
96
|
+
property :title
|
97
|
+
|
98
|
+
collection :songs, class: Song do
|
99
|
+
property :title
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
album = Album.new(title: "True North", songs: [Song.new(title: "The Island"), Song.new(:title => "Changing Tide")])
|
104
|
+
album.extend(AlbumRepresenter)
|
105
|
+
puts album.to_json
|
106
|
+
|
107
|
+
|
108
|
+
|
109
|
+
album = Album.new
|
110
|
+
album.extend(AlbumRepresenter)
|
111
|
+
album.from_json('{"title":"True North","songs":[{"title":"The Island"},{"title":"Changing Tide"}]}')
|
112
|
+
puts album.title
|
113
|
+
puts album.songs.first.title
|
114
|
+
|
115
|
+
# parse_strategy: :sync
|
116
|
+
|
117
|
+
reset_representer(AlbumRepresenter)
|
118
|
+
|
119
|
+
module AlbumRepresenter
|
120
|
+
include Roar::Representer::JSON
|
121
|
+
|
122
|
+
property :title
|
123
|
+
|
124
|
+
collection :songs, extend: SongRepresenter, parse_strategy: :sync
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
album = Album.new(title: "True North", songs: [Song.new(title: "The Island"), Song.new(:title => "Changing Tide")])
|
129
|
+
album.extend(AlbumRepresenter)
|
130
|
+
|
131
|
+
puts album.songs[0].object_id
|
132
|
+
album.from_json('{"title":"True North","songs":[{"title":"Secret Society"},{"title":"Changing Tide"}]}')
|
133
|
+
puts album.songs[0].title
|
134
|
+
puts album.songs[0].object_id##
|
135
|
+
|
136
|
+
# Coercion, renaming, ..
|
137
|
+
|
138
|
+
# Hypermedia
|
139
|
+
|
140
|
+
reset_representer(SongRepresenter)
|
141
|
+
|
142
|
+
module SongRepresenter
|
143
|
+
include Roar::Representer::JSON
|
144
|
+
include Roar::Representer::Feature::Hypermedia
|
145
|
+
|
146
|
+
property :title
|
147
|
+
|
148
|
+
link :self do
|
149
|
+
"http://songs/#{title}"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
song.extend(SongRepresenter)
|
154
|
+
puts song.to_json
|
155
|
+
|
156
|
+
# roar-rails and URL helpers
|
157
|
+
|
158
|
+
|
159
|
+
# Passing options into link
|
160
|
+
|
161
|
+
reset_representer(SongRepresenter)
|
162
|
+
|
163
|
+
module SongRepresenter
|
164
|
+
include Roar::Representer::JSON
|
165
|
+
|
166
|
+
property :title
|
167
|
+
|
168
|
+
link :self do |opts|
|
169
|
+
"http://#{opts[:base_url]}songs/#{title}"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
song.extend(SongRepresenter)
|
174
|
+
puts song.to_json(base_url: "localhost:3001/")
|
175
|
+
|
176
|
+
|
177
|
+
# Discovering Hypermedia
|
178
|
+
|
179
|
+
song = Song.new.extend(SongRepresenter)
|
180
|
+
song.from_json('{"title":"Roxanne","links":[{"rel":"self","href":"http://songs/Roxanne"}]}')
|
181
|
+
puts song.links[:self].href
|
182
|
+
|
183
|
+
# Media Formats: HAL
|
184
|
+
|
185
|
+
require 'roar/representer/json/hal'
|
186
|
+
|
187
|
+
module HAL
|
188
|
+
module SongRepresenter
|
189
|
+
include Roar::Representer::JSON::HAL
|
190
|
+
|
191
|
+
property :title
|
192
|
+
|
193
|
+
link :self do
|
194
|
+
"http://songs/#{title}"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
song.extend(HAL::SongRepresenter)
|
200
|
+
puts song.to_json
|
201
|
+
|
202
|
+
reset_representer(AlbumRepresenter)
|
203
|
+
|
204
|
+
module AlbumRepresenter
|
205
|
+
include Roar::Representer::JSON::HAL
|
206
|
+
|
207
|
+
property :title
|
208
|
+
|
209
|
+
collection :songs, class: Song, embedded: true do
|
210
|
+
property :title
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
album = Album.new(title: "True North", songs: [Song.new(title: "The Island"), Song.new(:title => "Changing Tide")])
|
215
|
+
album.extend(AlbumRepresenter)
|
216
|
+
puts album.to_json
|
217
|
+
|
218
|
+
# Media Formats: JSON+Collection
|
219
|
+
|
220
|
+
require 'roar/representer/json/collection_json'
|
221
|
+
|
222
|
+
|
223
|
+
module Collection
|
224
|
+
module SongRepresenter
|
225
|
+
include Roar::Representer::JSON::CollectionJSON
|
226
|
+
version "1.0"
|
227
|
+
href { "http://localhost/songs/" }
|
228
|
+
|
229
|
+
property :title
|
230
|
+
|
231
|
+
items(:class => Song) do
|
232
|
+
href { "//songs/#{title}" }
|
233
|
+
|
234
|
+
property :title, :prompt => "Song title"
|
235
|
+
|
236
|
+
link(:download) { "//songs/#{title}.mp3" }
|
237
|
+
end
|
238
|
+
|
239
|
+
template do
|
240
|
+
property :title, :prompt => "Song title"
|
241
|
+
end
|
242
|
+
|
243
|
+
queries do
|
244
|
+
link :search do
|
245
|
+
{:href => "//search", :data => [{:name => "q", :value => ""}]}
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
song = Song.new(title: "Roxanne")
|
252
|
+
song.extend(Collection::SongRepresenter)
|
253
|
+
puts song.to_json
|
254
|
+
|
255
|
+
|
256
|
+
|
257
|
+
# Client-side
|
258
|
+
# share in gem, parse existing document.
|
259
|
+
|
260
|
+
reset_representer(SongRepresenter)
|
261
|
+
|
262
|
+
module SongRepresenter
|
263
|
+
include Roar::Representer::JSON
|
264
|
+
include Roar::Representer::Feature::Hypermedia
|
265
|
+
|
266
|
+
property :title
|
267
|
+
property :id
|
268
|
+
|
269
|
+
link :self do
|
270
|
+
"http://songs/#{title}"
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
|
275
|
+
require 'roar/representer/feature/client'
|
276
|
+
|
277
|
+
module Client
|
278
|
+
class Song < OpenStruct
|
279
|
+
include Roar::Representer::JSON
|
280
|
+
include SongRepresenter
|
281
|
+
include Roar::Representer::Feature::Client
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
song = Client::Song.new(title: "Roxanne")
|
286
|
+
song.post("http://localhost:4567/songs", "application/json")
|
287
|
+
puts song.id
|
288
|
+
|
289
|
+
|
290
|
+
song = Client::Song.new
|
291
|
+
song.get("http://localhost:4567/songs/1", "application/json")
|
292
|
+
puts song.title
|
293
|
+
puts song.links[:self].href
|
294
|
+
|
295
|
+
# XML
|
296
|
+
|
297
|
+
require 'roar/representer/xml'
|
298
|
+
|
299
|
+
module XML
|
300
|
+
module SongRepresenter
|
301
|
+
include Roar::Representer::XML
|
302
|
+
include Roar::Representer::Feature::Hypermedia
|
303
|
+
|
304
|
+
property :title
|
305
|
+
property :id
|
306
|
+
|
307
|
+
link :self do
|
308
|
+
"http://songs/#{title}"
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
song = Song.new(title: "Roxanne", id: 42)
|
314
|
+
song.extend(XML::SongRepresenter)
|
315
|
+
puts song.to_xml
|
316
|
+
|
317
|
+
# Coercion
|
318
|
+
|
319
|
+
reset_representer(SongRepresenter)
|
320
|
+
|
321
|
+
require 'roar/representer/feature/coercion'
|
322
|
+
|
323
|
+
module SongRepresenter
|
324
|
+
include Roar::Representer::JSON
|
325
|
+
include Roar::Representer::Feature::Coercion
|
326
|
+
|
327
|
+
property :title
|
328
|
+
property :released_at, type: DateTime
|
329
|
+
end
|
330
|
+
|
331
|
+
song = Song.new
|
332
|
+
song.extend(SongRepresenter)
|
333
|
+
song.from_json('{"released_at":"1981/03/31"}')
|
334
|
+
|
335
|
+
puts song.released_at
|
336
|
+
|
337
|
+
|
338
|
+
class LinkOptionsCollection < Array
|
339
|
+
|
340
|
+
end
|
341
|
+
|
342
|
+
module HyperlinkiRepresenter
|
343
|
+
include Roar::Representer::JSON
|
344
|
+
|
345
|
+
def to_hash(*) # setup the link
|
346
|
+
# FIXME: why does self.to_s throw a stack level too deep (SystemStackError) ?
|
347
|
+
"#{self}"
|
348
|
+
# how would the Link instance get access to its Definition in order to execute the block?
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
module Representer
|
353
|
+
include Roar::Representer::JSON
|
354
|
+
|
355
|
+
def self.links
|
356
|
+
[:self, :next]
|
357
|
+
end
|
358
|
+
|
359
|
+
collection :links, :extend => HyperlinkiRepresenter
|
360
|
+
|
361
|
+
def links
|
362
|
+
# get link configurations from representable_attrs object.
|
363
|
+
#self.representable_attrs.links
|
364
|
+
LinkOptionsCollection.new(["self", "next"])
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
puts "".extend(Representer).to_json
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
require "sinatra"
|
3
|
+
require "ostruct"
|
4
|
+
require "roar/representer/json"
|
5
|
+
|
6
|
+
|
7
|
+
get "/method" do
|
8
|
+
"<method>get</method>"
|
9
|
+
end
|
10
|
+
|
11
|
+
post "/songs" do
|
12
|
+
'{"id":"1","title":"Roxanne","links":[{"rel":"self","href":"http://localhost/songs/1"}]}'
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
get "/songs/1" do
|
17
|
+
'{"id":"1","title":"Roxanne","links":[{"rel":"self","href":"http://localhost/songs/1"}]}'
|
18
|
+
end
|
data/lib/roar/representer/xml.rb
CHANGED
data/lib/roar/version.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
require 'test_helper'
|
2
|
-
require 'roar/representer/feature/hypermedia'
|
3
|
-
require 'roar/representer/json'
|
4
2
|
|
5
3
|
class HypermediaTest < MiniTest::Spec
|
6
4
|
describe "Hypermedia Feature" do
|
7
5
|
|
6
|
+
let (:song) { Song.new(:title => "Brandy Wine") }
|
8
7
|
|
9
8
|
before do
|
10
9
|
@bookmarks = Class.new do
|
@@ -29,23 +28,65 @@ class HypermediaTest < MiniTest::Spec
|
|
29
28
|
|
30
29
|
|
31
30
|
describe "#to_xml" do
|
32
|
-
it "
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
31
|
+
it "works when no links defined" do
|
32
|
+
repr = Module.new do
|
33
|
+
include Roar::Representer::XML
|
34
|
+
include Roar::Representer::Feature::Hypermedia
|
35
|
+
|
36
|
+
self.representation_wrap = "song"
|
37
|
+
property :title
|
38
|
+
end
|
39
|
+
|
40
|
+
song.extend(repr).to_xml.must_equal_xml "<song><title>Brandy Wine</title></song>"
|
38
41
|
end
|
39
42
|
|
40
|
-
|
41
|
-
|
43
|
+
let (:rpr) { Module.new do
|
44
|
+
include Roar::Representer::XML
|
45
|
+
include Roar::Representer::Feature::Hypermedia
|
46
|
+
|
47
|
+
self.representation_wrap = "song"
|
48
|
+
property :title
|
49
|
+
|
50
|
+
link(:self) { "/songs/#{title}" }
|
51
|
+
link(:all) { "/songs" }
|
52
|
+
end }
|
53
|
+
|
54
|
+
it "includes links in rendered document" do
|
55
|
+
song.extend(rpr).to_xml.must_equal_xml %{
|
56
|
+
<song>
|
57
|
+
<title>Brandy Wine</title>
|
58
|
+
<link rel="self" href="/songs/Brandy Wine"/>
|
59
|
+
<link rel="all" href="/songs"/>
|
60
|
+
</song>}
|
42
61
|
end
|
43
62
|
|
44
|
-
it "
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
63
|
+
it "suppresses links when links: false" do
|
64
|
+
song.extend(rpr).to_xml(:links => false).must_equal_xml "<song><title>Brandy Wine</title></song>"
|
65
|
+
end
|
66
|
+
|
67
|
+
it "renders nested links" do
|
68
|
+
song_rpr = rpr
|
69
|
+
|
70
|
+
album_rpr = Module.new do
|
71
|
+
include Roar::Representer::XML
|
72
|
+
include Roar::Representer::Feature::Hypermedia
|
73
|
+
|
74
|
+
self.representation_wrap = "album"
|
75
|
+
collection :songs, :extend => song_rpr
|
76
|
+
|
77
|
+
link(:self) { "/albums/mixed" }
|
78
|
+
end
|
79
|
+
|
80
|
+
Album.new(:songs => [song]).extend(album_rpr).to_xml.must_equal_xml %{
|
81
|
+
<album>
|
82
|
+
<song>
|
83
|
+
<title>Brandy Wine</title>
|
84
|
+
<link rel="self" href="/songs/Brandy Wine"/>
|
85
|
+
<link rel="all" href="/songs"/>
|
86
|
+
</song>
|
87
|
+
<link rel="self" href="/albums/mixed"/>
|
88
|
+
</album>
|
89
|
+
}
|
49
90
|
end
|
50
91
|
end
|
51
92
|
|
@@ -122,7 +163,7 @@ end
|
|
122
163
|
class LinkCollectionTest < MiniTest::Spec
|
123
164
|
describe "LinkCollection" do
|
124
165
|
subject { Roar::Representer::Feature::Hypermedia::LinkCollection.new }
|
125
|
-
|
166
|
+
|
126
167
|
describe "#add" do
|
127
168
|
it "keys by using rel string" do
|
128
169
|
subject.size.must_equal 0
|