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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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