representors 0.0.5

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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +18 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +126 -0
  5. data/LICENSE.md +19 -0
  6. data/README.md +28 -0
  7. data/Rakefile +10 -0
  8. data/lib/representor_support/utilities.rb +39 -0
  9. data/lib/representors.rb +5 -0
  10. data/lib/representors/errors.rb +7 -0
  11. data/lib/representors/field.rb +108 -0
  12. data/lib/representors/options.rb +67 -0
  13. data/lib/representors/representor.rb +161 -0
  14. data/lib/representors/representor_builder.rb +64 -0
  15. data/lib/representors/representor_hash.rb +59 -0
  16. data/lib/representors/serialization.rb +4 -0
  17. data/lib/representors/serialization/deserializer_base.rb +29 -0
  18. data/lib/representors/serialization/deserializer_factory.rb +13 -0
  19. data/lib/representors/serialization/hal_deserializer.rb +44 -0
  20. data/lib/representors/serialization/hal_serializer.rb +91 -0
  21. data/lib/representors/serialization/hale_deserializer.rb +162 -0
  22. data/lib/representors/serialization/hale_serializer.rb +110 -0
  23. data/lib/representors/serialization/serialization_base.rb +27 -0
  24. data/lib/representors/serialization/serialization_factory_base.rb +54 -0
  25. data/lib/representors/serialization/serializer_base.rb +20 -0
  26. data/lib/representors/serialization/serializer_factory.rb +17 -0
  27. data/lib/representors/transition.rb +130 -0
  28. data/lib/representors/version.rb +4 -0
  29. data/spec/fixtures/complex_hal.json +92 -0
  30. data/spec/fixtures/complex_hale_document.json +81 -0
  31. data/spec/fixtures/drds_hash.rb +120 -0
  32. data/spec/fixtures/hale_spec_examples/basic.json +77 -0
  33. data/spec/fixtures/hale_spec_examples/complex_reference_objects.json +157 -0
  34. data/spec/fixtures/hale_spec_examples/data.json +17 -0
  35. data/spec/fixtures/hale_spec_examples/data_objects.json +96 -0
  36. data/spec/fixtures/hale_spec_examples/link_objects.json +18 -0
  37. data/spec/fixtures/hale_spec_examples/nested_ref.json +43 -0
  38. data/spec/fixtures/hale_spec_examples/reference_objects.json +89 -0
  39. data/spec/fixtures/hale_tutorial_examples/basic_links.json +85 -0
  40. data/spec/fixtures/hale_tutorial_examples/basic_links_with_orders.json +96 -0
  41. data/spec/fixtures/hale_tutorial_examples/basic_links_with_references.json +108 -0
  42. data/spec/fixtures/hale_tutorial_examples/embedded.json +182 -0
  43. data/spec/fixtures/hale_tutorial_examples/empty.json +1 -0
  44. data/spec/fixtures/hale_tutorial_examples/enctype.json +14 -0
  45. data/spec/fixtures/hale_tutorial_examples/final.json +141 -0
  46. data/spec/fixtures/hale_tutorial_examples/get_link.json +17 -0
  47. data/spec/fixtures/hale_tutorial_examples/get_link_with_data.json +29 -0
  48. data/spec/fixtures/hale_tutorial_examples/links.json +11 -0
  49. data/spec/fixtures/hale_tutorial_examples/links_only.json +3 -0
  50. data/spec/fixtures/hale_tutorial_examples/meta.json +208 -0
  51. data/spec/fixtures/hale_tutorial_examples/self_link.json +7 -0
  52. data/spec/fixtures/single_drd.rb +266 -0
  53. data/spec/lib/representors/complex_representor_spec.rb +288 -0
  54. data/spec/lib/representors/field_spec.rb +141 -0
  55. data/spec/lib/representors/representor_builder_spec.rb +223 -0
  56. data/spec/lib/representors/representor_spec.rb +285 -0
  57. data/spec/lib/representors/serialization/deserializer_factory_spec.rb +118 -0
  58. data/spec/lib/representors/serialization/hal_deserializer_spec.rb +34 -0
  59. data/spec/lib/representors/serialization/hal_serializer_spec.rb +171 -0
  60. data/spec/lib/representors/serialization/hale_deserializer_spec.rb +59 -0
  61. data/spec/lib/representors/serialization/hale_roundtrip_spec.rb +34 -0
  62. data/spec/lib/representors/serialization/hale_serializer_spec.rb +659 -0
  63. data/spec/lib/representors/serialization/serializer_factory_spec.rb +108 -0
  64. data/spec/lib/representors/transition_spec.rb +349 -0
  65. data/spec/spec_helper.rb +32 -0
  66. data/spec/support/basic-hale.json +12 -0
  67. data/spec/support/hal_representor_shared.rb +206 -0
  68. data/spec/support/helpers.rb +8 -0
  69. data/tasks/benchmark.rake +75 -0
  70. data/tasks/complex_hal_document.json +98 -0
  71. data/tasks/test_specs.rake +37 -0
  72. data/tasks/yard.rake +22 -0
  73. metadata +232 -0
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ module Representors
4
+ describe HaleDeserializer do
5
+ it 'inherits from HalDeserializer' do
6
+ expect(HaleDeserializer.class.ancestors.include? HalDeserializer)
7
+ end
8
+
9
+ it 'provides the media type application/vnd.hale+json' do
10
+ expect(HaleDeserializer.media_types).to include('application/vnd.hale+json')
11
+ end
12
+
13
+ it 'provides the media symbol :hale' do
14
+ expect(HaleDeserializer.media_symbols).to include(:hale)
15
+ end
16
+
17
+ describe "#to_representor" do
18
+ it_behaves_like 'can create a representor from a hal document' do
19
+ subject(:deserializer) {Representors::HaleDeserializer.new(document)}
20
+ end
21
+
22
+ context '_meta with properties and links' do
23
+ subject(:deserializer) {Representors::HaleDeserializer.new(document)}
24
+ let(:semantics) { { 'title' => 'The Neverending Story'}}
25
+ let(:transition_rel) { 'author'}
26
+ let(:transition_href) { '/mike'}
27
+ let(:document) do
28
+ {
29
+ 'title' => 'The Neverending Story',
30
+ '_links' => {
31
+ 'author' => {'href' => '/mike'}
32
+ },
33
+ '_meta' => {
34
+ 'any' => { 'json' => 'thing'}
35
+ }
36
+ }
37
+ end
38
+ let(:semantics_field) {deserializer.to_representor.properties}
39
+ let(:transitions_field) {deserializer.to_representor.transitions}
40
+ let(:embedded_field) {deserializer.to_representor.embedded }
41
+
42
+ it 'return a hash with all the attributes of the document' do
43
+ expect(semantics_field).to eq(semantics)
44
+ end
45
+ it 'Create a transition with the link' do
46
+ expect(transitions_field.size).to eq(1)
47
+ expect(transitions_field.first.rel).to eq(transition_rel)
48
+ expect(transitions_field.first.uri).to eq(transition_href)
49
+ end
50
+ it 'does not return any embedded resource' do
51
+ expect(embedded_field).to be_empty
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ module Representors
4
+
5
+ describe 'Hale round trips' do
6
+ after do
7
+ deserialized_representor = HaleDeserializer.new(@file).to_representor
8
+ serialized_hale = Serialization::HaleSerializer.new(deserialized_representor).to_media_type
9
+ redeserialized_representor = HaleDeserializer.new(serialized_hale).to_representor
10
+ reserialized_hale = Serialization::HaleSerializer.new(redeserialized_representor).to_media_type
11
+ expect(JSON.parse(serialized_hale)).to eq(JSON.parse(reserialized_hale))
12
+ end
13
+
14
+ it 'round trips a simple document' do
15
+ @file = File.read("#{SPEC_DIR}/support/basic-hale.json")
16
+ end
17
+
18
+ it 'round trips a complex document' do
19
+ @file = File.read("#{SPEC_DIR}/fixtures/complex_hale_document.json")
20
+ end
21
+
22
+ Dir["#{SPEC_DIR}/fixtures/hale_spec_examples/*.json"].each do |path|
23
+ it "round trips the hale spec #{path[/hale_spec_examples\/(.*?)\.json/, 1]} document" do
24
+ @file = File.read(path)
25
+ end
26
+ end
27
+
28
+ Dir["#{SPEC_DIR}/fixtures/hale_tutorial_examples/*.json"].each do |path|
29
+ it "round trips the hale tutorial #{path[/hale_tutorial_examples\/(.*?)\.json/, 1]} document" do
30
+ @file = File.read(path)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,659 @@
1
+ require 'spec_helper'
2
+ require 'yaml'
3
+ require 'uri'
4
+
5
+ #TODO: This should only test functionality of this class and not repeat the file hal_spec
6
+ module Representors
7
+ describe Serialization::HaleSerializer do
8
+ before do
9
+ @base_representor = {
10
+ protocol: 'http',
11
+ href: 'www.example.com/drds',
12
+ id: 'drds',
13
+ doc: 'doc'
14
+ }
15
+ @options = {}
16
+ end
17
+
18
+ subject(:serializer) { SerializerFactory.build(:hale_json, Representor.new(document)) }
19
+ let(:result) {JSON.parse(serializer.to_media_type(@options))}
20
+
21
+ shared_examples "a hale documents attributes" do |representor_hash, media|
22
+ let(:document) { RepresentorHash.new(representor_hash).merge(@base_representor) }
23
+
24
+ representor_hash[:attributes].each do |k, v|
25
+ it "includes the document attribute #{k} and associated value" do
26
+ expect(result[k]).to eq(v[:value])
27
+ end
28
+ end
29
+ end
30
+
31
+ shared_examples "a hale documents links" do |representor_hash, media|
32
+ let(:document) { representor_hash.merge(@base_representor) }
33
+ representor = Representor.new(representor_hash)
34
+
35
+ representor.transitions.each do |item|
36
+ it "includes the document transition #{item}" do
37
+ expect(result["_links"][item[:rel]]['href']).to eq(item.templated_uri)
38
+ end
39
+ end
40
+
41
+ representor.meta_links.each do |transition|
42
+ it "includes meta link #{transition}" do
43
+ expect(result["_links"][transition.rel.to_s]['href']).to eq(transition.templated_uri)
44
+ end
45
+ end
46
+ end
47
+
48
+ shared_examples "a hale documents embedded hale documents" do |representor_hash, media|
49
+ let(:document) { representor_hash.merge(@base_representor) }
50
+
51
+ representor_hash[:embedded].each do |embed_name, embed|
52
+ embed[:attributes].each do |k, v|
53
+ it "includes the document attribute #{k} and associated value" do
54
+ expect(result["_embedded"][embed_name][k]).to eq(v[:value])
55
+ end
56
+ end
57
+ embed[:transitions].each do |item|
58
+ it "includes the document attribute #{item} and associated value" do
59
+ expect(result["_embedded"][embed_name]["_links"][item[:rel]]['href']).to eq(item[:href])
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ shared_examples "a hale documents embedded collection" do |representor_hash, media|
66
+ let(:document) { representor_hash.merge(@base_representor) }
67
+
68
+ representor_hash[:embedded].each do |embed_name, embeds|
69
+ embeds.each_with_index do |embed, index|
70
+ embed[:attributes].each do |k, v|
71
+ it "includes the document attribute #{k} and associated value" do
72
+ expect(result["_embedded"][embed_name][index][k]).to eq(v[:value])
73
+ end
74
+ end
75
+ embed[:transitions].each do |item|
76
+ it "includes the document attribute #{item} and associated value" do
77
+ expect(result["_embedded"][embed_name][index]["_links"][item[:rel]]['href']).to eq(item[:href])
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ describe '#to_media_type' do
85
+ context 'Document that is empty' do
86
+ let(:document) { {} }
87
+
88
+ it 'returns a hash with no attributes, links or embedded resources' do
89
+ expect(result).to eq({})
90
+ end
91
+ end
92
+
93
+ context 'Document with only self link' do
94
+ representor_hash = begin
95
+ {
96
+ transitions: [{rel: 'self', href: "www.example.com/coffeebucks/"}]
97
+ }
98
+ end
99
+
100
+ it_behaves_like 'a hale documents links', representor_hash, @options
101
+ end
102
+
103
+ context 'Document with only self and profile link' do
104
+ representor_hash = begin
105
+ {
106
+ links: {
107
+ profile: 'http://www.example.com/drds/show/DRDs',
108
+ },
109
+ transitions: [{rel: 'self', href: "www.example.com/coffeebucks/"}]
110
+ }
111
+ end
112
+
113
+ it_behaves_like 'a hale documents links', representor_hash, @options
114
+ end
115
+
116
+ context 'Document with GET link' do
117
+ representor_hash = begin
118
+ {
119
+ links: {
120
+ profile: 'http://www.example.com/drds/show/DRDs',
121
+ },
122
+ transitions: [
123
+ {rel: 'self', href: "www.example.com/coffeebucks/"},
124
+ {rel: "next", href: "www.example.com/coffeebucks?page=2"},
125
+ {
126
+ rel: 'orders',
127
+ href: "www.example.com/coffeebucks{?order_status,page}",
128
+ descriptors: {
129
+ "order_status" => {
130
+ scope: "href",
131
+ options: {
132
+ 'list' => [
133
+ "pending_payment",
134
+ "preparing",
135
+ "fulfilled"
136
+ ],},
137
+ :validators => [
138
+ "in"
139
+ ],
140
+ cardinality: "multiple",
141
+ },
142
+ "page" => {
143
+ scope: "href",
144
+ type: "integer",
145
+ :validators => [
146
+ {"min" => 1},
147
+ {"max" => 2},
148
+ ],
149
+ value: 1
150
+ }
151
+ }
152
+ }
153
+ ]
154
+ }
155
+ end
156
+
157
+ it_behaves_like 'a hale documents links', representor_hash, @options
158
+ let(:document) { representor_hash.merge(@base_representor) }
159
+ it 'has the expected Hale output' do
160
+ link_data = result["_links"]["orders"]["data"]
161
+ expect(link_data["order_status"]["scope"]).to eq("href")
162
+ expect(link_data["order_status"]["in"]).to eq(true)
163
+ expect(link_data["order_status"]["multi"]).to eq(true)
164
+ expect(link_data["order_status"]["options"]).to eq([
165
+ "pending_payment",
166
+ "preparing",
167
+ "fulfilled"
168
+ ])
169
+
170
+ expect(link_data["page"]["scope"]).to eq("href")
171
+ expect(link_data["page"]["type"]).to eq("integer")
172
+ expect(link_data["page"]["min"]).to eq(1)
173
+ expect(link_data["page"]["max"]).to eq(2)
174
+ expect(link_data["page"]["value"]).to eq(1)
175
+ end
176
+ end
177
+
178
+ context 'Document with POST link' do
179
+ representor_hash = begin
180
+ {
181
+ links: {
182
+ profile: 'http://www.example.com/drds/show/DRDs',
183
+ },
184
+ transitions: [
185
+ {rel: 'self', href: "www.example.com/coffeebucks/"},
186
+ {
187
+ rel: 'place_order',
188
+ method: 'POST',
189
+ href: "www.example.com/coffeebucks/orders",
190
+ descriptors: {
191
+ "drink_type" => {
192
+ options: {
193
+ 'list' => [
194
+ "coffee",
195
+ "americano",
196
+ "latte",
197
+ "mocha",
198
+ "cappuccino",
199
+ "macchiato",
200
+ "espresso"
201
+ ],},
202
+ :validators => [
203
+ "in",
204
+ "required"
205
+ ],
206
+ cardinality: "multiple",
207
+ },
208
+ "iced" => {
209
+ type: "boolean",
210
+ value: false
211
+ },
212
+ "size" => {
213
+ type: "integer",
214
+ field_type: "number",
215
+ options: {
216
+ 'hash' => {
217
+ "small" => 8,
218
+ "medium" => 12,
219
+ "large" => 16,
220
+ "extra-large" => 20
221
+ },},
222
+ :validators => [
223
+ "in",
224
+ "required"
225
+ ],
226
+ },
227
+ "shots" => {
228
+ type: "integer",
229
+ field_type: "range",
230
+ :validators => [
231
+ {"min" => 0},
232
+ {"max" => 16},
233
+ ],
234
+ },
235
+ "decaf" => {
236
+ type: "integer",
237
+ field_type: "range",
238
+ :validators => [
239
+ {"min" => 0},
240
+ {"max" => 16},
241
+ ],
242
+ }
243
+ }
244
+ }
245
+ ]
246
+ }
247
+ end
248
+
249
+ it_behaves_like 'a hale documents links', representor_hash, @options
250
+ let(:document) { representor_hash.merge(@base_representor) }
251
+ it 'has the expected Hale output' do
252
+ link_data = result["_links"]["place_order"]["data"]
253
+ expect(link_data["drink_type"]["in"]).to eq(true)
254
+ expect(link_data["drink_type"]["required"]).to eq(true)
255
+ expect(link_data["drink_type"]["options"]).to eq([
256
+ "coffee",
257
+ "americano",
258
+ "latte",
259
+ "mocha",
260
+ "cappuccino",
261
+ "macchiato",
262
+ "espresso"
263
+ ])
264
+
265
+ expect(link_data["iced"]["type"]).to eq("boolean")
266
+ expect(link_data["iced"]["value"]).to eq(false)
267
+
268
+ expect(link_data["size"]["type"]).to eq("integer:number")
269
+ expect(link_data["size"]["in"]).to eq(true)
270
+ expect(link_data["size"]["required"]).to eq(true)
271
+ expect(link_data["size"]["options"]).to eq([
272
+ {"small" => 8},
273
+ {"medium" => 12},
274
+ {"large" => 16},
275
+ {"extra-large" => 20}
276
+ ])
277
+
278
+ expect(link_data["shots"]["type"]).to eq("integer:range")
279
+ expect(link_data["shots"]["min"]).to eq(0)
280
+ expect(link_data["shots"]["max"]).to eq(16)
281
+
282
+ expect(link_data["decaf"]["type"]).to eq("integer:range")
283
+ expect(link_data["decaf"]["min"]).to eq(0)
284
+ expect(link_data["decaf"]["max"]).to eq(16)
285
+ end
286
+ end
287
+
288
+ context 'Document with properties and links' do
289
+ representor_hash = begin
290
+ {
291
+ attributes: {
292
+ "count" => {value: 3},
293
+ "total_count" => {value: 6},
294
+ },
295
+ transitions: [
296
+ {rel: 'self', href: "www.example.com/coffeebucks/"},
297
+ {rel: "next", href: "www.example.com/coffeebucks?page=2"},
298
+ {
299
+ rel: 'orders',
300
+ href: "www.example.com/coffeebucks",
301
+ descriptors: {
302
+ "order_status" => {
303
+ scope: "href",
304
+ options: {
305
+ 'list' => [
306
+ "pending_payment",
307
+ "preparing",
308
+ "fulfilled"
309
+ ],},
310
+ :validators => [
311
+ "in"
312
+ ],
313
+ cardinality: "multiple",
314
+ },
315
+ "page" => {
316
+ scope: "href",
317
+ type: "integer",
318
+ :validators => [
319
+ {"min" => 1},
320
+ {"max" => 2},
321
+ ],
322
+ value: 1
323
+ }
324
+ }
325
+ }
326
+ ]
327
+ }
328
+ end
329
+
330
+ it_behaves_like 'a hale documents attributes', representor_hash, @options
331
+ it_behaves_like 'a hale documents links', representor_hash, @options
332
+ end
333
+
334
+ context 'Document with properties' do
335
+ representor_hash = begin
336
+ {
337
+ attributes: {
338
+ "count" => {value: 3},
339
+ "total_count" => {value: 6},
340
+ },
341
+ }
342
+ end
343
+
344
+ it_behaves_like 'a hale documents attributes', representor_hash, @options
345
+ end
346
+
347
+
348
+ context 'Document with properties, links, and embedded' do
349
+ representor_hash = begin
350
+ {
351
+ attributes: {
352
+ 'title' => {value: 'The Neverending Story'},
353
+ },
354
+ transitions: [
355
+ {
356
+ href: '/mike',
357
+ rel: 'author',
358
+ }
359
+ ],
360
+ embedded: {
361
+ 'embedded_book' => {attributes: {'content' => { value: 'A...' } }, transitions: [{rel: 'self', href: '/foo'}]}
362
+ }
363
+ }
364
+ end
365
+
366
+ it_behaves_like 'a hale documents attributes', representor_hash, @options
367
+ it_behaves_like 'a hale documents links', representor_hash, @options
368
+ it_behaves_like 'a hale documents embedded hale documents', representor_hash, @options
369
+ end
370
+
371
+ context 'Document with an embedded collection' do
372
+ embedded = [{
373
+ transitions: [
374
+ {rel: "self", href: "www.example.com/coffeebucks/1"},
375
+ {
376
+ rel: 'fulfill',
377
+ href: "www.example.com/coffeebucks/1",
378
+ method: "PUT",
379
+ descriptors: {
380
+ "status" => {
381
+ value: "fulfilled",
382
+ validators: [
383
+ "required"
384
+ ],
385
+ }
386
+ }
387
+ }
388
+ ],
389
+ attributes: {
390
+ "status" => {value: "preparing"},
391
+ "created" => {value: 12569537329},
392
+ "drink_type" => {value: "latte"},
393
+ "iced" => {value: "true"},
394
+ "size" => {value: 8},
395
+ "shots" => {value: 2},
396
+ "decaf" => {value: 1}
397
+ }
398
+ },{
399
+ transitions: [
400
+ {rel: "self", href: "www.example.com/coffeebucks/2"},
401
+ ],
402
+ attributes: {
403
+ "status" => {value: "fulfilled"},
404
+ "created" => {value: 12569537312},
405
+ "drink_type" => {value: "latte"},
406
+ "iced" => {value: "true"},
407
+ "size" => {value: 8},
408
+ "shots" => {value: 2},
409
+ "decaf" => {value: 1}
410
+ }
411
+ },{
412
+ transitions: [
413
+ {rel: "self", href: "www.example.com/coffeebucks/3"},
414
+ {
415
+ rel: "cancel",
416
+ href: "www.example.com/coffeebucks/3",
417
+ method: "DELETE"
418
+ },
419
+ {
420
+ rel: 'prepare',
421
+ href: "www.example.com/coffeebucks/3",
422
+ method: "PUT",
423
+ descriptors: {
424
+ "status" => {
425
+ value: "preparing",
426
+ validators: [
427
+ "required"
428
+ ],
429
+ },
430
+ "paid" => {
431
+ value: 495,
432
+ validators: [
433
+ "required"
434
+ ],
435
+ }
436
+ }
437
+ }
438
+ ],
439
+ attributes: {
440
+ "status" => {value: "pending_payment"},
441
+ "created" => {value: 12569534536},
442
+ "drink_type" => {value: "latte"},
443
+ "iced" => {value: "true"},
444
+ "size" => {value: 8},
445
+ "shots" => {value: 2},
446
+ "decaf" => {value: 1},
447
+ "cost" => {value: 495}
448
+ }
449
+ },]
450
+
451
+
452
+ representor_hash = begin
453
+ {
454
+ embedded: {
455
+ "order_list" => embedded
456
+ }
457
+ }
458
+ end
459
+
460
+ let(:embedded_transitions) { {transitions: [
461
+ {:href=>"www.example.com/coffeebucks/1", :rel=>"order_list"},
462
+ {:href=>"www.example.com/coffeebucks/2", :rel=>"order_list"},
463
+ {:href=>"www.example.com/coffeebucks/3", :rel=>"order_list"}
464
+ ]
465
+ }
466
+ }
467
+
468
+ it 'does not add embedded links if they already exist' do
469
+ representor = Representor.new(representor_hash.merge(@base_representor).merge(embedded_transitions))
470
+ serialized_hale = JSON.parse(Serialization::HaleSerializer.new(representor).to_media_type)
471
+ expect(serialized_hale["_links"]["order_list"].count).to eq(3)
472
+ end
473
+
474
+ it_behaves_like 'a hale documents embedded collection', representor_hash, @options
475
+ end
476
+
477
+ context 'Document with an complex object' do
478
+ representor_hash = begin
479
+ {
480
+ attributes: {
481
+ "count" => {value: 3},
482
+ "total_count" => {value: 6},
483
+ },
484
+ transitions: [{
485
+ rel: 'multi_order',
486
+ href: "www.example.com/coffeebucks/orders",
487
+ request_encoding: "application/json",
488
+ method: "POST",
489
+ descriptors: {
490
+ "multi_order" => {
491
+ value: true
492
+ },
493
+ "orders" => {
494
+ type: "object",
495
+ cardinality: "multiple",
496
+ descriptors: {
497
+ "drink_type" => {
498
+ options: {
499
+ 'list' => [
500
+ "coffee",
501
+ "americano",
502
+ "latte",
503
+ "mocha",
504
+ "cappuccino",
505
+ "macchiato",
506
+ "espresso"
507
+ ],},
508
+ :validators => [
509
+ "in",
510
+ "required"
511
+ ],
512
+ cardinality: "multiple",
513
+ },
514
+ "iced" => {
515
+ type: "boolean",
516
+ value: false
517
+ },
518
+ "size" => {
519
+ type: "integer",
520
+ field_type: "number",
521
+ options: {
522
+ 'hash' => {
523
+ "small" => 8,
524
+ "medium" => 12,
525
+ "large" => 16,
526
+ "extra-large" => 20
527
+ },},
528
+ :validators => [
529
+ "in",
530
+ "required"
531
+ ],
532
+ },
533
+ "shots" => {
534
+ type: "integer",
535
+ field_type: "range",
536
+ :validators => [
537
+ {"min" => 0},
538
+ {"max" => 16},
539
+ ],
540
+ },
541
+ "decaf" => {
542
+ type: "integer",
543
+ field_type: "range",
544
+ :validators => [
545
+ {"min" => 0},
546
+ {"max" => 16},
547
+ ],
548
+ }
549
+ }
550
+ }
551
+ }
552
+ }]
553
+ }
554
+ end
555
+
556
+ it_behaves_like 'a hale documents attributes', representor_hash, @options
557
+ it_behaves_like 'a hale documents links', representor_hash, @options
558
+
559
+ let(:document) { representor_hash.merge(@base_representor) }
560
+ it 'has the expected Hale output' do
561
+ link_data = result["_links"]["multi_order"]["data"]["orders"]["data"]
562
+ expect(link_data["drink_type"]["in"]).to eq(true)
563
+ expect(link_data["drink_type"]["required"]).to eq(true)
564
+ expect(link_data["drink_type"]["options"]).to eq([
565
+ "coffee",
566
+ "americano",
567
+ "latte",
568
+ "mocha",
569
+ "cappuccino",
570
+ "macchiato",
571
+ "espresso"
572
+ ])
573
+
574
+ expect(link_data["iced"]["type"]).to eq("boolean")
575
+ expect(link_data["iced"]["value"]).to eq(false)
576
+
577
+ expect(link_data["size"]["type"]).to eq("integer:number")
578
+ expect(link_data["size"]["in"]).to eq(true)
579
+ expect(link_data["size"]["required"]).to eq(true)
580
+ expect(link_data["size"]["options"]).to eq([
581
+ {"small" => 8},
582
+ {"medium" => 12},
583
+ {"large" => 16},
584
+ {"extra-large" => 20}
585
+ ])
586
+
587
+ expect(link_data["shots"]["type"]).to eq("integer:range")
588
+ expect(link_data["shots"]["min"]).to eq(0)
589
+ expect(link_data["shots"]["max"]).to eq(16)
590
+
591
+ expect(link_data["decaf"]["type"]).to eq("integer:range")
592
+ expect(link_data["decaf"]["min"]).to eq(0)
593
+ expect(link_data["decaf"]["max"]).to eq(16)
594
+ end
595
+
596
+ end
597
+
598
+ context 'Document has a link data objects' do
599
+ representor_hash = begin
600
+ {
601
+ transitions: [
602
+ {
603
+ href: '/mike{?name}',
604
+ rel: 'author',
605
+ method: 'post',
606
+ descriptors: {
607
+ 'name' => {
608
+ type: 'text',
609
+ scope: 'href',
610
+ value: 'Bob',
611
+ options: {'list' => ['Bob', 'Jane', 'Mike'], 'id' => 'names'},
612
+ validators: [ "required" ]
613
+ }
614
+ }
615
+ }
616
+ ]
617
+ }
618
+ end
619
+
620
+ let(:document) { representor_hash.merge(@base_representor) }
621
+ let(:serialized_result) { JSON.parse(serializer.to_media_type) }
622
+
623
+ after do
624
+ expect(@result_element).to eq(@document_element)
625
+ end
626
+
627
+ it 'returns the correct link method' do
628
+ @result_element = serialized_result["_links"]['author']['method']
629
+ @document_element = document[:transitions].first[:method]
630
+ end
631
+
632
+ it 'returns the correct type keyword in link data' do
633
+ @result_element = serialized_result["_links"]['author']['data']['name']['type']
634
+ @document_element = document[:transitions].first[:descriptors]['name'][:type]
635
+ end
636
+
637
+ it 'returns the correct scope keyword in link data' do
638
+ @result_element = serialized_result["_links"]['author']['data']['name']['scope']
639
+ @document_element = document[:transitions].first[:descriptors]['name'][:scope]
640
+ end
641
+
642
+ it 'returns the correct value keyword in link data' do
643
+ @result_element = serialized_result["_links"]['author']['data']['name']['value']
644
+ @document_element = document[:transitions].first[:descriptors]['name'][:value]
645
+ end
646
+
647
+ it 'returns the correct datalists' do
648
+ @result_element = serialized_result["_meta"]['names']
649
+ @document_element = document[:transitions].first[:descriptors]['name'][:options]['list']
650
+ end
651
+
652
+ it 'properly references the datalists' do
653
+ @result_element = serialized_result["_links"]['author']['data']['name']['options']['_ref']
654
+ @document_element = ['names']
655
+ end
656
+ end
657
+ end
658
+ end
659
+ end