forestadmin-jsonapi-serializers 2.0.0.pre.beta.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ module ForestAdmin
2
+ module JSONAPI
3
+ module Serializer
4
+ VERSION = '2.0.0-beta.2'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,1337 @@
1
+ require 'active_model/errors'
2
+ require 'active_model/naming'
3
+ require 'active_model/translation'
4
+ require 'pry'
5
+
6
+ describe ForestAdmin::JSONAPI::Serializer do
7
+ def serialize_primary(object, options = {})
8
+ # Note: intentional high-coupling to protected method for tests.
9
+ ForestAdmin::JSONAPI::Serializer.send(:serialize_primary, object, options)
10
+ end
11
+
12
+ describe 'internal-only serialize_primary' do
13
+ it 'serializes nil to nil' do
14
+ # Spec: Primary data MUST be either:
15
+ # - a single resource object or null, for requests that target single resources
16
+ # http://jsonapi.org/format/#document-structure-top-level
17
+ primary_data = serialize_primary(nil, {serializer: MyApp::PostSerializer})
18
+ expect(primary_data).to be_nil
19
+ end
20
+
21
+ it 'can serialize primary data for a simple object' do
22
+ post = create(:post)
23
+ primary_data = serialize_primary(post, {serializer: MyApp::SimplestPostSerializer})
24
+ expect(primary_data).to eq({
25
+ 'id' => '1',
26
+ 'type' => 'posts',
27
+ 'attributes' => {
28
+ 'title' => 'Title for Post 1',
29
+ 'long-content' => 'Body for Post 1',
30
+ },
31
+ 'links' => {
32
+ 'self' => '/posts/1',
33
+ },
34
+ })
35
+ end
36
+
37
+ it 'can serialize primary data for a simple object with a long name' do
38
+ long_comment = create(:long_comment, post: create(:post))
39
+ primary_data = serialize_primary(long_comment, {serializer: MyApp::LongCommentSerializer})
40
+ expect(primary_data).to eq({
41
+ 'id' => '1',
42
+ 'type' => 'long-comments',
43
+ 'attributes' => {
44
+ 'body' => 'Body for LongComment 1',
45
+ },
46
+ 'links' => {
47
+ 'self' => '/long-comments/1',
48
+ },
49
+ 'relationships' => {
50
+ 'user' => {
51
+ 'links' => {
52
+ 'self' => '/long-comments/1/relationships/user',
53
+ 'related' => '/long-comments/1/user',
54
+ },
55
+ },
56
+ 'post' => {
57
+ 'links' => {
58
+ 'self' => '/long-comments/1/relationships/post',
59
+ 'related' => '/long-comments/1/post',
60
+ },
61
+ },
62
+ },
63
+ })
64
+ end
65
+
66
+ it 'can serialize primary data for a simple object with resource-level metadata' do
67
+ post = create(:post)
68
+ primary_data = serialize_primary(post, {serializer: MyApp::PostSerializerWithMetadata})
69
+ expect(primary_data).to eq({
70
+ 'id' => '1',
71
+ 'type' => 'posts',
72
+ 'attributes' => {
73
+ 'title' => 'Title for Post 1',
74
+ 'long-content' => 'Body for Post 1',
75
+ },
76
+ 'links' => {
77
+ 'self' => '/posts/1',
78
+ },
79
+ 'meta' => {
80
+ 'copyright' => 'Copyright 2015 Example Corp.',
81
+ 'authors' => [
82
+ 'Aliens',
83
+ ],
84
+ },
85
+ })
86
+ end
87
+
88
+ context 'without any linkage includes (default)' do
89
+ it 'can serialize primary data for an object with to-one and to-many relationships' do
90
+ post = create(:post)
91
+ primary_data = serialize_primary(post, {serializer: MyApp::PostSerializer})
92
+ expect(primary_data).to eq({
93
+ 'id' => '1',
94
+ 'type' => 'posts',
95
+ 'attributes' => {
96
+ 'title' => 'Title for Post 1',
97
+ 'long-content' => 'Body for Post 1',
98
+ },
99
+ 'links' => {
100
+ 'self' => '/posts/1',
101
+ },
102
+ 'relationships' => {
103
+ # Both to-one and to-many links are present, but neither include linkage:
104
+ 'author' => {
105
+ 'links' => {
106
+ 'self' => '/posts/1/relationships/author',
107
+ 'related' => '/posts/1/author',
108
+ },
109
+ },
110
+ 'long-comments' => {
111
+ 'links' => {
112
+ 'self' => '/posts/1/relationships/long-comments',
113
+ 'related' => '/posts/1/long-comments',
114
+ },
115
+ },
116
+ },
117
+ })
118
+ end
119
+
120
+ it 'does not include relationship links if relationship_{self_link,_related_link} are nil' do
121
+ post = create(:post)
122
+ primary_data = serialize_primary(post, {serializer: MyApp::PostSerializerWithoutLinks})
123
+ expect(primary_data).to eq({
124
+ 'id' => '1',
125
+ 'type' => 'posts',
126
+ 'attributes' => {
127
+ 'title' => 'Title for Post 1',
128
+ 'long-content' => 'Body for Post 1',
129
+ },
130
+ # This is technically invalid since relationships MUST contain at least one of links,
131
+ # data, or meta, but we leave that up to the user.
132
+ 'relationships' => {
133
+ 'author' => {},
134
+ 'long-comments' => {},
135
+ },
136
+ })
137
+ end
138
+
139
+ it 'does not include id when it is nil' do
140
+ post = create(:post)
141
+ post.id = nil
142
+ primary_data = serialize_primary(post, {serializer: MyApp::PostSerializerWithoutLinks})
143
+ expect(primary_data).to eq({
144
+ 'type' => 'posts',
145
+ 'attributes' => {
146
+ 'title' => 'Title for Post 1',
147
+ 'long-content' => 'Body for Post 1',
148
+ },
149
+ 'relationships' => {
150
+ 'author' => {},
151
+ 'long-comments' => {},
152
+ },
153
+ })
154
+ end
155
+
156
+ it 'serializes object when multiple attributes are declared once' do
157
+ post = create(:post)
158
+ primary_data = serialize_primary(post, {serializer: MyApp::MultipleAttributesSerializer})
159
+ expect(primary_data).to eq({
160
+ 'id' => '1',
161
+ 'type' => 'posts',
162
+ 'attributes' => {
163
+ 'title' => 'Title for Post 1',
164
+ 'body' => 'Body for Post 1',
165
+ },
166
+ 'links' => {
167
+ 'self' => '/posts/1',
168
+ }
169
+ })
170
+ end
171
+ end
172
+
173
+ context 'with linkage includes' do
174
+ it 'can serialize primary data for a null to-one relationship' do
175
+ post = create(:post, author: nil)
176
+ options = {
177
+ serializer: MyApp::PostSerializer,
178
+ include_linkages: ['author', 'long-comments'],
179
+ }
180
+ primary_data = serialize_primary(post, options)
181
+ expect(primary_data).to eq({
182
+ 'id' => '1',
183
+ 'type' => 'posts',
184
+ 'attributes' => {
185
+ 'title' => 'Title for Post 1',
186
+ 'long-content' => 'Body for Post 1',
187
+ },
188
+ 'links' => {
189
+ 'self' => '/posts/1',
190
+ },
191
+ 'relationships' => {
192
+ 'author' => {
193
+ 'links' => {
194
+ 'self' => '/posts/1/relationships/author',
195
+ 'related' => '/posts/1/author',
196
+ },
197
+ # Spec: Resource linkage MUST be represented as one of the following:
198
+ # - null for empty to-one relationships.
199
+ # http://jsonapi.org/format/#document-structure-resource-relationships
200
+ 'data' => nil,
201
+ },
202
+ 'long-comments' => {
203
+ 'links' => {
204
+ 'self' => '/posts/1/relationships/long-comments',
205
+ 'related' => '/posts/1/long-comments',
206
+ },
207
+ 'data' => [],
208
+ },
209
+ },
210
+ })
211
+ end
212
+
213
+ it 'can serialize primary data for a simple to-one relationship' do
214
+ post = create(:post, :with_author)
215
+ options = {
216
+ serializer: MyApp::PostSerializer,
217
+ include_linkages: ['author', 'long-comments'],
218
+ }
219
+ primary_data = serialize_primary(post, options)
220
+ expect(primary_data).to eq({
221
+ 'id' => '1',
222
+ 'type' => 'posts',
223
+ 'attributes' => {
224
+ 'title' => 'Title for Post 1',
225
+ 'long-content' => 'Body for Post 1',
226
+ },
227
+ 'links' => {
228
+ 'self' => '/posts/1',
229
+ },
230
+ 'relationships' => {
231
+ 'author' => {
232
+ 'links' => {
233
+ 'self' => '/posts/1/relationships/author',
234
+ 'related' => '/posts/1/author',
235
+ },
236
+ # Spec: Resource linkage MUST be represented as one of the following:
237
+ # - a 'linkage object' (defined below) for non-empty to-one relationships.
238
+ # http://jsonapi.org/format/#document-structure-resource-relationships
239
+ 'data' => {
240
+ 'type' => 'users',
241
+ 'id' => '1',
242
+ },
243
+ },
244
+ 'long-comments' => {
245
+ 'links' => {
246
+ 'self' => '/posts/1/relationships/long-comments',
247
+ 'related' => '/posts/1/long-comments',
248
+ },
249
+ 'data' => [],
250
+ },
251
+ },
252
+ })
253
+ end
254
+
255
+ it 'can serialize primary data for an empty to-many relationship' do
256
+ post = create(:post, long_comments: [])
257
+ options = {
258
+ serializer: MyApp::PostSerializer,
259
+ include_linkages: ['author', 'long-comments'],
260
+ }
261
+ primary_data = serialize_primary(post, options)
262
+ expect(primary_data).to eq({
263
+ 'id' => '1',
264
+ 'type' => 'posts',
265
+ 'attributes' => {
266
+ 'title' => 'Title for Post 1',
267
+ 'long-content' => 'Body for Post 1',
268
+ },
269
+ 'links' => {
270
+ 'self' => '/posts/1',
271
+ },
272
+ 'relationships' => {
273
+ 'author' => {
274
+ 'links' => {
275
+ 'self' => '/posts/1/relationships/author',
276
+ 'related' => '/posts/1/author',
277
+ },
278
+ 'data' => nil,
279
+ },
280
+ 'long-comments' => {
281
+ 'links' => {
282
+ 'self' => '/posts/1/relationships/long-comments',
283
+ 'related' => '/posts/1/long-comments',
284
+ },
285
+ # Spec: Resource linkage MUST be represented as one of the following:
286
+ # - an empty array ([]) for empty to-many relationships.
287
+ # http://jsonapi.org/format/#document-structure-resource-relationships
288
+ 'data' => [],
289
+ },
290
+ },
291
+ })
292
+ end
293
+
294
+ it 'can serialize primary data for a simple to-many relationship' do
295
+ long_comments = create_list(:long_comment, 2)
296
+ post = create(:post, long_comments: long_comments)
297
+ options = {
298
+ serializer: MyApp::PostSerializer,
299
+ include_linkages: ['author', 'long-comments'],
300
+ }
301
+ primary_data = serialize_primary(post, options)
302
+ expect(primary_data).to eq({
303
+ 'id' => '1',
304
+ 'type' => 'posts',
305
+ 'attributes' => {
306
+ 'title' => 'Title for Post 1',
307
+ 'long-content' => 'Body for Post 1',
308
+ },
309
+ 'links' => {
310
+ 'self' => '/posts/1',
311
+ },
312
+ 'relationships' => {
313
+ 'author' => {
314
+ 'links' => {
315
+ 'self' => '/posts/1/relationships/author',
316
+ 'related' => '/posts/1/author',
317
+ },
318
+ 'data' => nil,
319
+ },
320
+ 'long-comments' => {
321
+ 'links' => {
322
+ 'self' => '/posts/1/relationships/long-comments',
323
+ 'related' => '/posts/1/long-comments',
324
+ },
325
+ # Spec: Resource linkage MUST be represented as one of the following:
326
+ # - an array of linkage objects for non-empty to-many relationships.
327
+ # http://jsonapi.org/format/#document-structure-resource-relationships
328
+ 'data' => [
329
+ {
330
+ 'type' => 'long-comments',
331
+ 'id' => '1',
332
+ },
333
+ {
334
+ 'type' => 'long-comments',
335
+ 'id' => '2',
336
+ },
337
+ ],
338
+ },
339
+ },
340
+ })
341
+ end
342
+ end
343
+
344
+ it 'can serialize primary data for an empty serializer with no attributes' do
345
+ post = create(:post)
346
+ primary_data = serialize_primary(post, {serializer: MyApp::EmptySerializer})
347
+ expect(primary_data).to eq({
348
+ 'id' => '1',
349
+ 'type' => 'posts',
350
+ 'links' => {
351
+ 'self' => '/posts/1',
352
+ },
353
+ })
354
+ end
355
+
356
+ it 'can find the correct serializer by object class name' do
357
+ post = create(:post)
358
+ primary_data = serialize_primary(post)
359
+ expect(primary_data).to eq({
360
+ 'id' => '1',
361
+ 'type' => 'posts',
362
+ 'attributes' => {
363
+ 'title' => 'Title for Post 1',
364
+ 'long-content' => 'Body for Post 1',
365
+ },
366
+ 'links' => {
367
+ 'self' => '/posts/1',
368
+ },
369
+ 'relationships' => {
370
+ 'author' => {
371
+ 'links' => {
372
+ 'self' => '/posts/1/relationships/author',
373
+ 'related' => '/posts/1/author',
374
+ },
375
+ },
376
+ 'long-comments' => {
377
+ 'links' => {
378
+ 'self' => '/posts/1/relationships/long-comments',
379
+ 'related' => '/posts/1/long-comments',
380
+ },
381
+ },
382
+ },
383
+ })
384
+ end
385
+
386
+ it 'allows to exclude linkages for relationships' do
387
+ long_comments = create_list(:long_comment, 2)
388
+ post = create(:post, :with_author, long_comments: long_comments)
389
+ primary_data = serialize_primary(post, { serializer: MyApp::PostSerializerWithoutIncludeLinks })
390
+ expect(primary_data).to eq({
391
+ 'id' => '1',
392
+ 'type' => 'posts',
393
+ 'attributes' => {
394
+ 'title' => 'Title for Post 1'
395
+ },
396
+ 'links' => {
397
+ 'self' => '/posts/1',
398
+ },
399
+ 'relationships' => {
400
+ 'author' => {
401
+ },
402
+ 'long-comments' => {
403
+ },
404
+ }
405
+ })
406
+ end
407
+
408
+ it 'allows to include data for relationships' do
409
+ long_comments = create_list(:long_comment, 2)
410
+ post = create(:post, :with_author, long_comments: long_comments)
411
+ primary_data = serialize_primary(post, { serializer: MyApp::PostSerializerWithIncludeData })
412
+ expect(primary_data).to eq({
413
+ 'id' => '1',
414
+ 'type' => 'posts',
415
+ 'attributes' => {
416
+ 'title' => 'Title for Post 1'
417
+ },
418
+ 'links' => {
419
+ 'self' => '/posts/1',
420
+ },
421
+ 'relationships' => {
422
+ 'author' => {
423
+ 'links' => {
424
+ 'self' => '/posts/1/relationships/author',
425
+ 'related' => '/posts/1/author'
426
+ },
427
+ 'data' => {
428
+ 'type' => 'users',
429
+ 'id' => '1'
430
+ }
431
+ },
432
+ 'long-comments' => {
433
+ 'links' => {
434
+ 'self' => '/posts/1/relationships/long-comments',
435
+ 'related' => '/posts/1/long-comments'
436
+ },
437
+ 'data' => [
438
+ {
439
+ 'type' => 'long-comments',
440
+ 'id' => '1'
441
+ },
442
+ {
443
+ 'type' => 'long-comments',
444
+ 'id' => '2'
445
+ }
446
+ ]
447
+ },
448
+ }
449
+ })
450
+ end
451
+ end
452
+
453
+ # The members data and errors MUST NOT coexist in the same document.
454
+ describe 'ForestAdmin::JSONAPI::Serializer.serialize_errors' do
455
+ it 'can include a top level errors node' do
456
+ errors = [
457
+ {
458
+ 'source' => {'pointer' => '/data/attributes/first-name'},
459
+ 'title' => 'Invalid Attribute',
460
+ 'detail' => 'First name must contain at least three characters.'
461
+ },
462
+ {
463
+ 'source' => {'pointer' => '/data/attributes/first-name'},
464
+ 'title' => 'Invalid Attribute',
465
+ 'detail' => 'First name must contain an emoji.'
466
+ }
467
+ ]
468
+ expect(ForestAdmin::JSONAPI::Serializer.serialize_errors(errors)).to eq({'errors' => errors})
469
+ end
470
+
471
+ it 'works for active_record' do
472
+ # DummyUser exists so we can test calling user.errors.to_hash(full_messages: true)
473
+ class DummyUser
474
+ extend ActiveModel::Naming
475
+ extend ActiveModel::Translation
476
+
477
+ def initialize
478
+ @errors = ActiveModel::Errors.new(self)
479
+ @errors.add(:email, :invalid, message: 'is invalid')
480
+ @errors.add(:email, :blank, message: "can't be blank")
481
+ @errors.add(:first_name, :blank, message: "can't be blank")
482
+ end
483
+
484
+ attr_accessor :first_name, :email
485
+ attr_reader :errors
486
+
487
+ def read_attribute_for_validation(attr)
488
+ send(attr)
489
+ end
490
+ end
491
+ user = DummyUser.new
492
+ jsonapi_errors = [
493
+ {
494
+ 'source' => {'pointer' => '/data/attributes/email'},
495
+ 'detail' => 'Email is invalid'
496
+ },
497
+ {
498
+ 'source' => {'pointer' => '/data/attributes/email'},
499
+ 'detail' => "Email can't be blank"
500
+ },
501
+ {
502
+ 'source' => {'pointer' => '/data/attributes/first-name'},
503
+ 'detail' => "First name can't be blank"
504
+ }
505
+ ]
506
+ expect(ForestAdmin::JSONAPI::Serializer.serialize_errors(user.errors)).to eq({
507
+ 'errors' => jsonapi_errors,
508
+ })
509
+ end
510
+ end
511
+
512
+ describe 'ForestAdmin::JSONAPI::Serializer.serialize' do
513
+ # The following tests rely on the fact that serialize_primary has been tested above, so object
514
+ # primary data is not explicitly tested here. If things are broken, look above here first.
515
+
516
+ it 'can serialize a nil object' do
517
+ expect(ForestAdmin::JSONAPI::Serializer.serialize(nil)).to eq({'data' => nil})
518
+ end
519
+
520
+ it 'can serialize a nil object with includes' do
521
+ # Also, the include argument is not validated in this case because we don't know the type.
522
+ data = ForestAdmin::JSONAPI::Serializer.serialize(nil, include: ['fake'])
523
+ expect(data).to eq({'data' => nil, 'included' => []})
524
+ end
525
+
526
+ it 'can serialize an empty array' do
527
+ # Also, the include argument is not validated in this case because we don't know the type.
528
+ data = ForestAdmin::JSONAPI::Serializer.serialize([], is_collection: true, include: ['fake'])
529
+ expect(data).to eq({'data' => [], 'included' => []})
530
+ end
531
+
532
+ it 'can serialize a simple object' do
533
+ post = create(:post)
534
+ expect(ForestAdmin::JSONAPI::Serializer.serialize(post)).to eq({
535
+ 'data' => serialize_primary(post, {serializer: MyApp::PostSerializer}),
536
+ })
537
+ end
538
+
539
+ it 'can include a top level jsonapi node' do
540
+ post = create(:post)
541
+ jsonapi_version = {'version' => '1.0'}
542
+ expect(ForestAdmin::JSONAPI::Serializer.serialize(post, jsonapi: jsonapi_version)).to eq({
543
+ 'jsonapi' => {'version' => '1.0'},
544
+ 'data' => serialize_primary(post, {serializer: MyApp::PostSerializer}),
545
+ })
546
+ end
547
+
548
+ it 'can include a top level meta node' do
549
+ post = create(:post)
550
+ meta = {authors: ['Yehuda Katz', 'Steve Klabnik'], copyright: 'Copyright 2015 Example Corp.'}
551
+ expect(ForestAdmin::JSONAPI::Serializer.serialize(post, meta: meta)).to eq({
552
+ 'meta' => meta,
553
+ 'data' => serialize_primary(post, {serializer: MyApp::PostSerializer}),
554
+ })
555
+ end
556
+
557
+ it 'can include a top level links node' do
558
+ post = create(:post)
559
+ links = {self: 'http://example.com/posts'}
560
+ expect(ForestAdmin::JSONAPI::Serializer.serialize(post, links: links)).to eq({
561
+ 'links' => links,
562
+ 'data' => serialize_primary(post, {serializer: MyApp::PostSerializer}),
563
+ })
564
+ end
565
+
566
+ # TODO: remove this code on next major release
567
+ it 'can include a top level errors node - deprecated' do
568
+ post = create(:post)
569
+ errors = [
570
+ {
571
+ "source" => { "pointer" => "/data/attributes/first-name" },
572
+ "title" => "Invalid Attribute",
573
+ "detail" => "First name must contain at least three characters."
574
+ },
575
+ {
576
+ "source" => { "pointer" => "/data/attributes/first-name" },
577
+ "title" => "Invalid Attribute",
578
+ "detail" => "First name must contain an emoji."
579
+ }
580
+ ]
581
+ expect(ForestAdmin::JSONAPI::Serializer.serialize(post, errors: errors)).to eq({
582
+ 'errors' => errors,
583
+ 'data' => serialize_primary(post, {serializer: MyApp::PostSerializer}),
584
+ })
585
+ end
586
+
587
+ it 'can serialize a single object with an `each` method by passing skip_collection_check: true' do
588
+ post = create(:post)
589
+ post.define_singleton_method(:each) do
590
+ "defining this just to defeat the duck-type check"
591
+ end
592
+
593
+ expect(ForestAdmin::JSONAPI::Serializer.serialize(post, skip_collection_check: true)).to eq({
594
+ 'data' => serialize_primary(post, {serializer: MyApp::PostSerializer}),
595
+ })
596
+ end
597
+
598
+ it 'can serialize a collection' do
599
+ posts = create_list(:post, 2)
600
+ expect(ForestAdmin::JSONAPI::Serializer.serialize(posts, is_collection: true)).to eq({
601
+ 'data' => [
602
+ serialize_primary(posts.first, {serializer: MyApp::PostSerializer}),
603
+ serialize_primary(posts.last, {serializer: MyApp::PostSerializer}),
604
+ ],
605
+ })
606
+ end
607
+
608
+ it 'raises AmbiguousCollectionError if is_collection is not passed' do
609
+ posts = create_list(:post, 2)
610
+ error = ForestAdmin::JSONAPI::Serializer::AmbiguousCollectionError
611
+ expect { ForestAdmin::JSONAPI::Serializer.serialize(posts) }.to raise_error(error)
612
+ end
613
+
614
+ it 'raises error if include is not named correctly' do
615
+ post = create(:post)
616
+ error = ForestAdmin::JSONAPI::Serializer::InvalidIncludeError
617
+ expect { ForestAdmin::JSONAPI::Serializer.serialize(post, include: ['long_comments']) }.to raise_error(error)
618
+ end
619
+
620
+ it 'can serialize a nil object when given serializer' do
621
+ options = {serializer: MyApp::PostSerializer}
622
+ expect(ForestAdmin::JSONAPI::Serializer.serialize(nil, options)).to eq({'data' => nil})
623
+ end
624
+
625
+ it 'can serialize an empty array when given serializer' do
626
+ options = {is_collection: true, serializer: MyApp::PostSerializer}
627
+ expect(ForestAdmin::JSONAPI::Serializer.serialize([], options)).to eq({'data' => []})
628
+ end
629
+
630
+ it 'can serialize a simple object when given serializer' do
631
+ post = create(:post)
632
+ options = {serializer: MyApp::SimplestPostSerializer}
633
+ expect(ForestAdmin::JSONAPI::Serializer.serialize(post, options)).to eq({
634
+ 'data' => serialize_primary(post, {serializer: MyApp::SimplestPostSerializer}),
635
+ })
636
+ end
637
+
638
+ it 'handles include of nil to-one relationship with compound document' do
639
+ post = create(:post)
640
+
641
+ expected_primary_data = serialize_primary(post, {
642
+ serializer: MyApp::PostSerializer,
643
+ include_linkages: ['author'],
644
+ })
645
+ expect(ForestAdmin::JSONAPI::Serializer.serialize(post, include: ['author'])).to eq({
646
+ 'data' => expected_primary_data,
647
+ 'included' => [],
648
+ })
649
+ end
650
+
651
+ it 'handles include of simple to-one relationship with compound document' do
652
+ post = create(:post, :with_author)
653
+
654
+ expected_primary_data = serialize_primary(post, {
655
+ serializer: MyApp::PostSerializer,
656
+ include_linkages: ['author'],
657
+ })
658
+ expect(ForestAdmin::JSONAPI::Serializer.serialize(post, include: ['author'])).to eq({
659
+ 'data' => expected_primary_data,
660
+ 'included' => [
661
+ serialize_primary(post.author, {serializer: MyAppOtherNamespace::UserSerializer}),
662
+ ],
663
+ })
664
+ end
665
+
666
+ it 'handles include of empty to-many relationships with compound document' do
667
+ post = create(:post, :with_author, long_comments: [])
668
+
669
+ expected_primary_data = serialize_primary(post, {
670
+ serializer: MyApp::PostSerializer,
671
+ include_linkages: ['long-comments'],
672
+ })
673
+ expect(ForestAdmin::JSONAPI::Serializer.serialize(post, include: ['long-comments'])).to eq({
674
+ 'data' => expected_primary_data,
675
+ 'included' => [],
676
+ })
677
+ end
678
+
679
+ it 'handles include of to-many relationships with compound document' do
680
+ long_comments = create_list(:long_comment, 2)
681
+ post = create(:post, :with_author, long_comments: long_comments)
682
+
683
+ expected_primary_data = serialize_primary(post, {
684
+ serializer: MyApp::PostSerializer,
685
+ include_linkages: ['long-comments'],
686
+ })
687
+ expect(ForestAdmin::JSONAPI::Serializer.serialize(post, include: ['long-comments'])).to eq({
688
+ 'data' => expected_primary_data,
689
+ 'included' => [
690
+ serialize_primary(long_comments.first, {serializer: MyApp::LongCommentSerializer}),
691
+ serialize_primary(long_comments.last, {serializer: MyApp::LongCommentSerializer}),
692
+ ],
693
+ })
694
+ end
695
+
696
+ it 'only includes one copy of each referenced relationship' do
697
+ long_comment = create(:long_comment)
698
+ long_comments = [long_comment, long_comment]
699
+ post = create(:post, :with_author, long_comments: long_comments)
700
+
701
+ expected_primary_data = serialize_primary(post, {
702
+ serializer: MyApp::PostSerializer,
703
+ include_linkages: ['long-comments'],
704
+ })
705
+ expect(ForestAdmin::JSONAPI::Serializer.serialize(post, include: ['long-comments'])).to eq({
706
+ 'data' => expected_primary_data,
707
+ 'included' => [
708
+ serialize_primary(long_comment, {serializer: MyApp::LongCommentSerializer}),
709
+ ],
710
+ })
711
+ end
712
+
713
+ it 'handles circular-referencing relationships with compound document' do
714
+ long_comments = create_list(:long_comment, 2)
715
+ post = create(:post, :with_author, long_comments: long_comments)
716
+
717
+ # Make sure each long-comment has a circular reference back to the post.
718
+ long_comments.each { |c| c.post = post }
719
+
720
+ expected_primary_data = serialize_primary(post, {
721
+ serializer: MyApp::PostSerializer,
722
+ include_linkages: ['long-comments'],
723
+ })
724
+ expect(ForestAdmin::JSONAPI::Serializer.serialize(post, include: ['long-comments'])).to eq({
725
+ 'data' => expected_primary_data,
726
+ 'included' => [
727
+ serialize_primary(post.long_comments.first, {serializer: MyApp::LongCommentSerializer}),
728
+ serialize_primary(post.long_comments.last, {serializer: MyApp::LongCommentSerializer}),
729
+ ],
730
+ })
731
+ end
732
+
733
+ it 'errors if include is not a defined attribute' do
734
+ user = create(:user)
735
+ expect { ForestAdmin::JSONAPI::Serializer.serialize(user, include: ['fake-attr']) }.to raise_error
736
+ end
737
+
738
+ it 'handles recursive loading of relationships' do
739
+ user = create(:user)
740
+ long_comments = create_list(:long_comment, 2, user: user)
741
+ post = create(:post, :with_author, long_comments: long_comments)
742
+ # Make sure each long-comment has a circular reference back to the post.
743
+ long_comments.each { |c| c.post = post }
744
+
745
+ expected_data = {
746
+ 'data' => serialize_primary(post, {
747
+ serializer: MyApp::PostSerializer,
748
+ include_linkages: ['long-comments']
749
+ }),
750
+ 'included' => [
751
+ # Intermediates are included: long-comments, long-comments.post, and long-comments.post.author
752
+ # http://jsonapi.org/format/#document-structure-compound-documents
753
+ serialize_primary(post.long_comments.first, {
754
+ serializer: MyApp::LongCommentSerializer,
755
+ include_linkages: ['post']
756
+ }),
757
+ serialize_primary(post.long_comments.last, {
758
+ serializer: MyApp::LongCommentSerializer,
759
+ include_linkages: ['post']
760
+ }),
761
+ serialize_primary(post, {
762
+ serializer: MyApp::PostSerializer,
763
+ include_linkages: ['author', 'post.long-comments', ]
764
+ }),
765
+ serialize_primary(post.author, {serializer: MyAppOtherNamespace::UserSerializer})
766
+ ],
767
+ }
768
+ includes = ['long-comments.post.author']
769
+ actual_data = ForestAdmin::JSONAPI::Serializer.serialize(post, include: includes)
770
+ # Multiple expectations for better diff output for debugging.
771
+ expect(actual_data['data']).to eq(expected_data['data'])
772
+ expect(actual_data['included']).to eq(expected_data['included'])
773
+ expect(actual_data).to eq(expected_data)
774
+ end
775
+
776
+ it 'handles recursive loading of multiple to-one relationships on children' do
777
+ first_user = create(:user)
778
+ second_user = create(:user)
779
+ first_comment = create(:long_comment, user: first_user)
780
+ second_comment = create(:long_comment, user: second_user)
781
+ long_comments = [first_comment, second_comment]
782
+ post = create(:post, :with_author, long_comments: long_comments)
783
+ # Make sure each long-comment has a circular reference back to the post.
784
+ long_comments.each { |c| c.post = post }
785
+
786
+ expected_data = {
787
+ 'data' => serialize_primary(post, {
788
+ serializer: MyApp::PostSerializer,
789
+ include_linkages: ['long-comments']
790
+ }),
791
+ 'included' => [
792
+ serialize_primary(first_comment, {
793
+ serializer: MyApp::LongCommentSerializer,
794
+ include_linkages: ['user']
795
+ }),
796
+ serialize_primary(second_comment, {
797
+ serializer: MyApp::LongCommentSerializer,
798
+ include_linkages: ['user']
799
+ }),
800
+ serialize_primary(first_user, {serializer: MyAppOtherNamespace::UserSerializer}),
801
+ serialize_primary(second_user, {serializer: MyAppOtherNamespace::UserSerializer}),
802
+ ],
803
+ }
804
+
805
+ includes = ['long-comments.user']
806
+ actual_data = ForestAdmin::JSONAPI::Serializer.serialize(post, include: includes)
807
+
808
+ # Multiple expectations for better diff output for debugging.
809
+ expect(actual_data['data']).to eq(expected_data['data'])
810
+ expect(actual_data['included']).to eq(expected_data['included'])
811
+ expect(actual_data).to eq(expected_data)
812
+ end
813
+
814
+ it 'includes linkage in compounded resources only if the immediate parent was also included' do
815
+ comment_user = create(:user)
816
+ long_comments = [create(:long_comment, user: comment_user)]
817
+ post = create(:post, :with_author, long_comments: long_comments)
818
+
819
+ expected_primary_data = serialize_primary(post, {
820
+ serializer: MyApp::PostSerializer,
821
+ include_linkages: ['long-comments'],
822
+ })
823
+ expected_data = {
824
+ 'data' => expected_primary_data,
825
+ 'included' => [
826
+ serialize_primary(long_comments.first, {
827
+ serializer: MyApp::LongCommentSerializer,
828
+ include_linkages: ['user'],
829
+ }),
830
+ # Note: post.author does not show up here because it was not included.
831
+ serialize_primary(comment_user, {serializer: MyAppOtherNamespace::UserSerializer}),
832
+ ],
833
+ }
834
+ includes = ['long-comments.user']
835
+ actual_data = ForestAdmin::JSONAPI::Serializer.serialize(post, include: includes)
836
+
837
+ # Multiple expectations for better diff output for debugging.
838
+ expect(actual_data['data']).to eq(expected_data['data'])
839
+ expect(actual_data['included']).to eq(expected_data['included'])
840
+ expect(actual_data).to eq(expected_data)
841
+ end
842
+
843
+ it 'handles recursive loading of to-many relationships with overlapping include paths' do
844
+ user = create(:user)
845
+ long_comments = create_list(:long_comment, 2, user: user)
846
+ post = create(:post, :with_author, long_comments: long_comments)
847
+ # Make sure each long-comment has a circular reference back to the post.
848
+ long_comments.each { |c| c.post = post }
849
+
850
+ expected_primary_data = serialize_primary(post, {
851
+ serializer: MyApp::PostSerializer,
852
+ include_linkages: ['long-comments'],
853
+ })
854
+ expected_data = {
855
+ 'data' => expected_primary_data,
856
+ 'included' => [
857
+ serialize_primary(long_comments.first, {
858
+ serializer: MyApp::LongCommentSerializer,
859
+ include_linkages: ['post'],
860
+ }),
861
+ serialize_primary(long_comments.last, {
862
+ serializer: MyApp::LongCommentSerializer,
863
+ include_linkages: ['post'],
864
+ }),
865
+ serialize_primary(post, {
866
+ serializer: MyApp::PostSerializer,
867
+ include_linkages: ['author'],
868
+ }),
869
+ serialize_primary(post.author, {serializer: MyAppOtherNamespace::UserSerializer}),
870
+ ],
871
+ }
872
+ # Also test that it handles string include arguments.
873
+ includes = 'long-comments,long-comments.post.author'
874
+ actual_data = ForestAdmin::JSONAPI::Serializer.serialize(post, include: includes)
875
+
876
+ # Multiple expectations for better diff output for debugging.
877
+ expect(actual_data['data']).to eq(expected_data['data'])
878
+ expect(actual_data['included']).to eq(expected_data['included'])
879
+ expect(actual_data).to eq(expected_data)
880
+ end
881
+
882
+ context 'on collection' do
883
+ it 'handles include of has_many relationships with compound document' do
884
+ long_comments = create_list(:long_comment, 2)
885
+ posts = create_list(:post, 2, :with_author, long_comments: long_comments)
886
+
887
+ expected_primary_data = ForestAdmin::JSONAPI::Serializer.send(:serialize_primary_multi, posts, {
888
+ serializer: MyApp::PostSerializer,
889
+ include_linkages: ['long-comments'],
890
+ })
891
+ data = ForestAdmin::JSONAPI::Serializer.serialize(posts, is_collection: true, include: ['long-comments'])
892
+ expect(data).to eq({
893
+ 'data' => expected_primary_data,
894
+ 'included' => [
895
+ serialize_primary(long_comments.first, {serializer: MyApp::LongCommentSerializer}),
896
+ serialize_primary(long_comments.last, {serializer: MyApp::LongCommentSerializer}),
897
+ ],
898
+ })
899
+ end
900
+ end
901
+
902
+ context 'sparse fieldsets' do
903
+ it 'allows to limit fields(attributes) for serialized resource' do
904
+ first_user = create(:user)
905
+ second_user = create(:user)
906
+ first_comment = create(:long_comment, user: first_user)
907
+ second_comment = create(:long_comment, user: second_user)
908
+ long_comments = [first_comment, second_comment]
909
+ post = create(:post, :with_author, long_comments: long_comments)
910
+
911
+ serialized_data = ForestAdmin::JSONAPI::Serializer.serialize(post, fields: {posts: :title})
912
+ expect(serialized_data).to eq ({
913
+ 'data' => {
914
+ 'type' => 'posts',
915
+ 'id' => post.id.to_s,
916
+ 'attributes' => {
917
+ 'title' => post.title,
918
+ },
919
+ 'links' => {
920
+ 'self' => '/posts/1'
921
+ }
922
+ }
923
+ })
924
+ end
925
+
926
+ it 'allows to limit fields(relationships) for serialized resource' do
927
+ first_user = create(:user)
928
+ second_user = create(:user)
929
+ first_comment = create(:long_comment, user: first_user)
930
+ second_comment = create(:long_comment, user: second_user)
931
+ long_comments = [first_comment, second_comment]
932
+ post = create(:post, :with_author, long_comments: long_comments)
933
+
934
+ fields = {posts: 'title,author,long-comments'}
935
+ serialized_data = ForestAdmin::JSONAPI::Serializer.serialize(post, fields: fields)
936
+ expect(serialized_data['data']['relationships']).to eq ({
937
+ 'author' => {
938
+ 'links' => {
939
+ 'self' => '/posts/1/relationships/author',
940
+ 'related' => '/posts/1/author'
941
+ }
942
+ },
943
+ 'long-comments' => {
944
+ 'links' => {
945
+ 'self' => '/posts/1/relationships/long-comments',
946
+ 'related' => '/posts/1/long-comments'
947
+ }
948
+ }
949
+ })
950
+ end
951
+
952
+ it "allows also to pass specific fields as array instead of comma-separates values" do
953
+ first_user = create(:user)
954
+ second_user = create(:user)
955
+ first_comment = create(:long_comment, user: first_user)
956
+ second_comment = create(:long_comment, user: second_user)
957
+ long_comments = [first_comment, second_comment]
958
+ post = create(:post, :with_author, long_comments: long_comments)
959
+
960
+ serialized_data = ForestAdmin::JSONAPI::Serializer.serialize(post, fields: {posts: ['title', 'author']})
961
+ expect(serialized_data['data']['attributes']).to eq ({
962
+ 'title' => post.title
963
+ })
964
+ expect(serialized_data['data']['relationships']).to eq ({
965
+ 'author' => {
966
+ 'links' => {
967
+ 'self' => '/posts/1/relationships/author',
968
+ 'related' => '/posts/1/author'
969
+ }
970
+ }
971
+ })
972
+ end
973
+
974
+ it 'allows to limit fields(attributes and relationships) for included resources' do
975
+ first_user = create(:user)
976
+ second_user = create(:user)
977
+ first_comment = create(:long_comment, user: first_user)
978
+ second_comment = create(:long_comment, user: second_user)
979
+ long_comments = [first_comment, second_comment]
980
+ post = create(:post, :with_author, long_comments: long_comments)
981
+
982
+ expected_primary_data = serialize_primary(post, {
983
+ serializer: MyApp::PostSerializer,
984
+ include_linkages: ['author'],
985
+ fields: {'posts' => [:title, :author] }
986
+ })
987
+
988
+ fields = {posts: 'title,author', users: ''}
989
+ serialized_data = ForestAdmin::JSONAPI::Serializer.serialize(post, fields: fields, include: 'author')
990
+ expect(serialized_data).to eq ({
991
+ 'data' => expected_primary_data,
992
+ 'included' => [
993
+ serialize_primary(
994
+ post.author,
995
+ serializer: MyAppOtherNamespace::UserSerializer,
996
+ fields: {'users' => []},
997
+ )
998
+ ]
999
+ })
1000
+
1001
+ fields = {posts: 'title,author'}
1002
+ serialized_data = ForestAdmin::JSONAPI::Serializer.serialize(post, fields: fields, include: 'author')
1003
+ expect(serialized_data).to eq ({
1004
+ 'data' => expected_primary_data,
1005
+ 'included' => [
1006
+ serialize_primary(post.author, serializer: MyAppOtherNamespace::UserSerializer)
1007
+ ]
1008
+ })
1009
+ end
1010
+ end
1011
+ end
1012
+
1013
+ describe 'serialize (class method)' do
1014
+ it 'delegates to module method but overrides serializer' do
1015
+ post = create(:post)
1016
+ expect(MyApp::SimplestPostSerializer.serialize(post)).to eq({
1017
+ 'data' => serialize_primary(post, {serializer: MyApp::SimplestPostSerializer}),
1018
+ })
1019
+ end
1020
+ end
1021
+
1022
+ describe 'internal-only parse_relationship_paths' do
1023
+ it 'correctly handles empty arrays' do
1024
+ result = ForestAdmin::JSONAPI::Serializer.send(:parse_relationship_paths, [])
1025
+ expect(result).to eq({})
1026
+ end
1027
+
1028
+ it 'correctly handles single-level relationship paths' do
1029
+ result = ForestAdmin::JSONAPI::Serializer.send(:parse_relationship_paths, ['foo'])
1030
+ expect(result).to eq({
1031
+ 'foo' => {_include: true}
1032
+ })
1033
+ end
1034
+
1035
+ it 'correctly handles multi-level relationship paths' do
1036
+ result = ForestAdmin::JSONAPI::Serializer.send(:parse_relationship_paths, ['foo.bar'])
1037
+ expect(result).to eq({
1038
+ 'foo' => {_include: true, 'bar' => {_include: true}}
1039
+ })
1040
+ end
1041
+
1042
+ it 'correctly handles multi-level relationship paths with same parent' do
1043
+ paths = ['foo', 'foo.bar']
1044
+ result = ForestAdmin::JSONAPI::Serializer.send(:parse_relationship_paths, paths)
1045
+ expect(result).to eq({
1046
+ 'foo' => {_include: true, 'bar' => {_include: true}}
1047
+ })
1048
+ end
1049
+
1050
+ it 'correctly handles multi-level relationship paths with different parent' do
1051
+ paths = ['foo', 'bar', 'bar.baz']
1052
+ result = ForestAdmin::JSONAPI::Serializer.send(:parse_relationship_paths, paths)
1053
+ expect(result).to eq({
1054
+ 'foo' => {_include: true},
1055
+ 'bar' => {_include: true, 'baz' => {_include: true}},
1056
+ })
1057
+ end
1058
+
1059
+ it 'correctly handles three-leveled path' do
1060
+ paths = ['foo', 'foo.bar', 'foo.bar.baz']
1061
+ result = ForestAdmin::JSONAPI::Serializer.send(:parse_relationship_paths, paths)
1062
+ expect(result).to eq({
1063
+ 'foo' => {_include: true, 'bar' => {_include: true, 'baz' => {_include: true}}}
1064
+ })
1065
+ end
1066
+
1067
+ it 'correctly handles three-leveled path with skipped middle' do
1068
+ paths = ['foo', 'foo.bar.baz']
1069
+ result = ForestAdmin::JSONAPI::Serializer.send(:parse_relationship_paths, paths)
1070
+ expect(result).to eq({
1071
+ 'foo' => {_include: true, 'bar' => {_include: true, 'baz' => {_include: true}}}
1072
+ })
1073
+ end
1074
+ end
1075
+
1076
+ describe 'if/unless handling with contexts' do
1077
+ it 'can be used to show/hide attributes' do
1078
+ post = create(:post)
1079
+ options = {serializer: MyApp::PostSerializerWithContext}
1080
+
1081
+ options[:context] = {show_body: false}
1082
+ data = ForestAdmin::JSONAPI::Serializer.serialize(post, options)
1083
+ expect(data['data']['attributes']).to_not have_key('body')
1084
+
1085
+ options[:context] = {show_body: true}
1086
+ data = ForestAdmin::JSONAPI::Serializer.serialize(post, options)
1087
+ expect(data['data']['attributes']).to have_key('body')
1088
+ expect(data['data']['attributes']['body']).to eq('Body for Post 1')
1089
+
1090
+ options[:context] = {hide_body: true}
1091
+ data = ForestAdmin::JSONAPI::Serializer.serialize(post, options)
1092
+ expect(data['data']['attributes']).to_not have_key('body')
1093
+
1094
+ options[:context] = {hide_body: false}
1095
+ data = ForestAdmin::JSONAPI::Serializer.serialize(post, options)
1096
+ expect(data['data']['attributes']).to have_key('body')
1097
+ expect(data['data']['attributes']['body']).to eq('Body for Post 1')
1098
+
1099
+ options[:context] = {show_body: false, hide_body: false}
1100
+ data = ForestAdmin::JSONAPI::Serializer.serialize(post, options)
1101
+ expect(data['data']['attributes']).to_not have_key('body')
1102
+
1103
+ options[:context] = {show_body: true, hide_body: false}
1104
+ data = ForestAdmin::JSONAPI::Serializer.serialize(post, options)
1105
+ expect(data['data']['attributes']).to have_key('body')
1106
+ expect(data['data']['attributes']['body']).to eq('Body for Post 1')
1107
+
1108
+ # Remember: attribute is configured as if: show_body?, unless: hide_body?
1109
+ # and the results should be logically AND'd together:
1110
+ options[:context] = {show_body: false, hide_body: true}
1111
+ data = ForestAdmin::JSONAPI::Serializer.serialize(post, options)
1112
+ expect(data['data']['attributes']).to_not have_key('body')
1113
+
1114
+ options[:context] = {show_body: true, hide_body: true}
1115
+ data = ForestAdmin::JSONAPI::Serializer.serialize(post, options)
1116
+ expect(data['data']['attributes']).to_not have_key('body')
1117
+ end
1118
+ end
1119
+
1120
+ describe 'context' do
1121
+ it 'is passed through all relationship serializers' do
1122
+ # Force long_comments to be serialized by the context-sensitive serializer.
1123
+ expect_any_instance_of(MyApp::LongComment).to receive(:jsonapi_serializer_class_name)
1124
+ .at_least(:once)
1125
+ .and_return('MyApp::LongCommentsSerializerWithContext')
1126
+
1127
+ user = create(:user, name: 'Long Comment Author -- Should Not Be Serialized')
1128
+ long_comment = create(:long_comment, user: user)
1129
+ post = create(:post, :with_author, long_comments: [long_comment])
1130
+
1131
+ context = {show_body: false, show_comments_user: false}
1132
+ expected_data = {
1133
+ 'data' => serialize_primary(post, {
1134
+ serializer: MyApp::PostSerializerWithContext,
1135
+ include_linkages: ['long-comments'],
1136
+ context: context,
1137
+ }),
1138
+ 'included' => [
1139
+ serialize_primary(long_comment, {
1140
+ serializer: MyApp::LongCommentsSerializerWithContext,
1141
+ context: context,
1142
+ }),
1143
+ ],
1144
+ }
1145
+ includes = ['long-comments']
1146
+ actual_data = ForestAdmin::JSONAPI::Serializer.serialize(post, context: context, include: includes)
1147
+ # Multiple expectations for better diff output for debugging.
1148
+ expect(actual_data['data']).to eq(expected_data['data'])
1149
+ expect(actual_data['included']).to eq(expected_data['included'])
1150
+ expect(actual_data).to eq(expected_data)
1151
+ end
1152
+ end
1153
+
1154
+ describe 'base_url' do
1155
+ it 'is empty by default' do
1156
+ long_comments = create_list(:long_comment, 1)
1157
+ post = create(:post, long_comments: long_comments)
1158
+ data = ForestAdmin::JSONAPI::Serializer.serialize(post)
1159
+ expect(data['data']['links']['self']).to eq('/posts/1')
1160
+ expect(data['data']['relationships']['author']['links']).to eq({
1161
+ 'self' => '/posts/1/relationships/author',
1162
+ 'related' => '/posts/1/author'
1163
+ })
1164
+ end
1165
+
1166
+ it 'adds base_url to links if passed' do
1167
+ long_comments = create_list(:long_comment, 1)
1168
+ post = create(:post, long_comments: long_comments)
1169
+ data = ForestAdmin::JSONAPI::Serializer.serialize(post, base_url: 'http://example.com')
1170
+ expect(data['data']['links']['self']).to eq('http://example.com/posts/1')
1171
+ expect(data['data']['relationships']['author']['links']).to eq({
1172
+ 'self' => 'http://example.com/posts/1/relationships/author',
1173
+ 'related' => 'http://example.com/posts/1/author'
1174
+ })
1175
+ end
1176
+
1177
+ it 'uses overriden base_url method if it exists' do
1178
+ long_comments = create_list(:long_comment, 1)
1179
+ post = create(:post, long_comments: long_comments)
1180
+ data = ForestAdmin::JSONAPI::Serializer.serialize(post, serializer: MyApp::PostSerializerWithBaseUrl)
1181
+ expect(data['data']['links']['self']).to eq('http://example.com/posts/1')
1182
+ expect(data['data']['relationships']['author']['links']).to eq({
1183
+ 'self' => 'http://example.com/posts/1/relationships/author',
1184
+ 'related' => 'http://example.com/posts/1/author'
1185
+ })
1186
+ end
1187
+ end
1188
+
1189
+ describe 'inheritance through subclassing' do
1190
+ it 'inherits attributes' do
1191
+ tagged_post = create(:tagged_post)
1192
+ options = {serializer: MyApp::PostSerializerWithInheritedProperties}
1193
+ data = ForestAdmin::JSONAPI::Serializer.serialize(tagged_post, options);
1194
+ expect(data['data']['attributes']['title']).to eq('Title for TaggedPost 1');
1195
+ expect(data['data']['attributes']['tag']).to eq('Tag for TaggedPost 1');
1196
+ end
1197
+
1198
+ it 'inherits relations' do
1199
+ long_comments = create_list(:long_comment, 2)
1200
+ tagged_post = create(:tagged_post, :with_author, long_comments: long_comments)
1201
+ options = {serializer: MyApp::PostSerializerWithInheritedProperties}
1202
+ data = ForestAdmin::JSONAPI::Serializer.serialize(tagged_post, options);
1203
+
1204
+ expect(data['data']['relationships']).to eq({
1205
+ 'author' => {
1206
+ 'links' => {
1207
+ 'self' => '/tagged-posts/1/relationships/author',
1208
+ 'related' => '/tagged-posts/1/author',
1209
+ },
1210
+ },
1211
+ 'long-comments' => {
1212
+ 'links' => {
1213
+ 'self' => '/tagged-posts/1/relationships/long-comments',
1214
+ 'related' => '/tagged-posts/1/long-comments',
1215
+ }
1216
+ }
1217
+ })
1218
+ end
1219
+ end
1220
+
1221
+ describe 'include validation' do
1222
+ it 'raises an exception when join character is invalid' do
1223
+ expect do
1224
+ ForestAdmin::JSONAPI::Serializer.serialize(create(:post), include: 'long_comments');
1225
+ end.to raise_error(ForestAdmin::JSONAPI::Serializer::InvalidIncludeError)
1226
+
1227
+ expect do
1228
+ ForestAdmin::JSONAPI::Serializer.serialize(create(:post), include: 'long-comments');
1229
+ end.not_to raise_error
1230
+
1231
+ expect do
1232
+ ForestAdmin::JSONAPI::Serializer.serialize(create(:underscore_test), include: 'tagged-posts');
1233
+ end.to raise_error(ForestAdmin::JSONAPI::Serializer::InvalidIncludeError)
1234
+
1235
+ expect do
1236
+ ForestAdmin::JSONAPI::Serializer.serialize(create(:underscore_test), include: 'tagged_posts');
1237
+ end.not_to raise_error
1238
+ end
1239
+ end
1240
+
1241
+ describe 'serializer with namespace option' do
1242
+ it 'can serialize a simple object with namespace Api::V1' do
1243
+ user = create(:user)
1244
+ expect(ForestAdmin::JSONAPI::Serializer.serialize(user, {namespace: Api::V1})).to eq({
1245
+ 'data' => serialize_primary(user, {serializer: Api::V1::MyApp::UserSerializer}),
1246
+ })
1247
+ end
1248
+
1249
+ it 'handles recursive loading of relationships with namespaces' do
1250
+ user = create(:user)
1251
+ long_comments = create_list(:long_comment, 2, user: user)
1252
+ post = create(:post, :with_author, long_comments: long_comments)
1253
+ # Make sure each long-comment has a circular reference back to the post.
1254
+ long_comments.each { |c| c.post = post }
1255
+
1256
+ expected_data = {
1257
+ 'data' => serialize_primary(post, {
1258
+ serializer: Api::V1::MyApp::PostSerializer,
1259
+ include_linkages: ['long-comments']
1260
+ }),
1261
+ 'included' => [
1262
+ # Intermediates are included: long-comments, long-comments.post, and long-comments.post.author
1263
+ # http://jsonapi.org/format/#document-structure-compound-documents
1264
+ serialize_primary(post.long_comments.first, {
1265
+ serializer: Api::V1::MyApp::LongCommentSerializer,
1266
+ include_linkages: ['post']
1267
+ }),
1268
+ serialize_primary(post.long_comments.last, {
1269
+ serializer: Api::V1::MyApp::LongCommentSerializer,
1270
+ include_linkages: ['post']
1271
+ }),
1272
+ serialize_primary(post, {
1273
+ serializer: Api::V1::MyApp::PostSerializer,
1274
+ include_linkages: ['author', 'post.long-comments']
1275
+ }),
1276
+ serialize_primary(post.author, {serializer: Api::V1::MyApp::UserSerializer})
1277
+ ],
1278
+ }
1279
+ includes = ['long-comments.post.author']
1280
+ actual_data = ForestAdmin::JSONAPI::Serializer.serialize(post, include: includes, namespace: Api::V1)
1281
+ # Multiple expectations for better diff output for debugging.
1282
+ expect(actual_data['data']).to eq(expected_data['data'])
1283
+ expect(actual_data['included']).to eq(expected_data['included'])
1284
+ expect(actual_data).to eq(expected_data)
1285
+ end
1286
+ end
1287
+
1288
+ describe 'instrumentation' do
1289
+ let(:post) { create(:post, :with_author) }
1290
+ let(:events) { [] }
1291
+
1292
+ before do
1293
+ ActiveSupport::Notifications.subscribe(notification_name) do |*args|
1294
+ events << ActiveSupport::Notifications::Event.new(*args)
1295
+ end
1296
+ end
1297
+
1298
+ describe 'serialize_primary' do
1299
+ let(:notification_name) { 'render.jsonapi_serializers.serialize_primary' }
1300
+
1301
+ it 'sends an event for a single serialize call' do
1302
+ ForestAdmin::JSONAPI::Serializer.serialize(post)
1303
+
1304
+ expect(events.length).to eq(1)
1305
+ expect(events[0].name).to eq(notification_name)
1306
+ expect(events[0].payload).to eq({class_name: "MyApp::Post"})
1307
+ end
1308
+
1309
+ it 'sends events for includes' do
1310
+ ForestAdmin::JSONAPI::Serializer.serialize(post, include: ['author'])
1311
+
1312
+ expect(events.length).to eq(2)
1313
+ expect(events[0].payload).to eq({class_name: "MyApp::Post"})
1314
+ expect(events[1].payload).to eq({class_name: "MyApp::User"})
1315
+ end
1316
+ end
1317
+
1318
+ describe 'find_recursive_relationships' do
1319
+ let(:notification_name) { 'render.jsonapi_serializers.find_recursive_relationships' }
1320
+
1321
+ it 'does not send event when there are no includes' do
1322
+ ForestAdmin::JSONAPI::Serializer.serialize(post)
1323
+ expect(events.length).to eq(0)
1324
+ end
1325
+
1326
+ it 'sends events for includes' do
1327
+ ForestAdmin::JSONAPI::Serializer.serialize(post, include: ['author'])
1328
+
1329
+ expect(events.length).to eq(2)
1330
+ expect(events[0].name).to eq(notification_name)
1331
+ expect(events[0].payload).to eq({class_name: "MyApp::User"})
1332
+ expect(events[1].name).to eq(notification_name)
1333
+ expect(events[1].payload).to eq({class_name: "MyApp::Post"})
1334
+ end
1335
+ end
1336
+ end
1337
+ end