representors 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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