File: associations_test.rb

package info (click to toggle)
ruby-active-model-serializers 0.10.12-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 1,752 kB
  • sloc: ruby: 13,138; sh: 53; makefile: 6
file content (520 lines) | stat: -rw-r--r-- 20,237 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
# frozen_string_literal: true

require 'test_helper'
module ActiveModel
  class Serializer
    class AssociationsTest < ActiveSupport::TestCase
      class ModelWithoutSerializer < ::Model
        attributes :id, :name
      end

      def setup
        @author = Author.new(name: 'Steve K.')
        @author.bio = nil
        @author.roles = []
        @blog = Blog.new(name: 'AMS Blog')
        @post = Post.new(title: 'New Post', body: 'Body')
        @tag = ModelWithoutSerializer.new(id: 'tagid', name: '#hashtagged')
        @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
        @post.comments = [@comment]
        @post.tags = [@tag]
        @post.blog = @blog
        @comment.post = @post
        @comment.author = nil
        @post.author = @author
        @author.posts = [@post]

        @post_serializer = PostSerializer.new(@post, custom_options: true)
        @author_serializer = AuthorSerializer.new(@author)
        @comment_serializer = CommentSerializer.new(@comment)
      end

      def test_has_many_and_has_one
        @author_serializer.associations.each do |association|
          key = association.key
          serializer = association.lazy_association.serializer

          case key
          when :posts
            assert_equal true, association.include_data?
            assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer)
          when :bio
            assert_equal true, association.include_data?
            assert_nil serializer
          when :roles
            assert_equal true, association.include_data?
            assert_kind_of(ActiveModelSerializers.config.collection_serializer, serializer)
          else
            flunk "Unknown association: #{key}"
          end
        end
      end

      def test_has_many_with_no_serializer
        post_serializer_class = Class.new(ActiveModel::Serializer) do
          attributes :id
          has_many :tags
        end
        post_serializer_class.new(@post).associations.each do |association|
          key = association.key
          serializer = association.lazy_association.serializer

          assert_equal :tags, key
          assert_nil serializer
          assert_equal [{ id: 'tagid', name: '#hashtagged' }].to_json, association.virtual_value.to_json
        end
      end

      def test_serializer_options_are_passed_into_associations_serializers
        association = @post_serializer
                      .associations
                      .detect { |assoc| assoc.key == :comments }

        comment_serializer = association.lazy_association.serializer.first
        class << comment_serializer
          def custom_options
            instance_options
          end
        end
        assert comment_serializer.custom_options.fetch(:custom_options)
      end

      def test_belongs_to
        @comment_serializer.associations.each do |association|
          key = association.key
          serializer = association.lazy_association.serializer

          case key
          when :post
            assert_kind_of(PostSerializer, serializer)
          when :author
            assert_nil serializer
          else
            flunk "Unknown association: #{key}"
          end

          assert_equal true, association.include_data?
        end
      end

      def test_belongs_to_with_custom_method
        assert(
          @post_serializer.associations.any? do |association|
            association.key == :blog
          end
        )
      end

      def test_associations_inheritance
        inherited_klass = Class.new(PostSerializer)

        assert_equal(PostSerializer._reflections, inherited_klass._reflections)
      end

      def test_associations_inheritance_with_new_association
        inherited_klass = Class.new(PostSerializer) do
          has_many :top_comments, serializer: CommentSerializer
        end

        assert(
          PostSerializer._reflections.values.all? do |reflection|
            inherited_klass._reflections.values.include?(reflection)
          end
        )

        assert(
          inherited_klass._reflections.values.any? do |reflection|
            reflection.name == :top_comments
          end
        )
      end

      def test_associations_custom_keys
        serializer = PostWithCustomKeysSerializer.new(@post)

        expected_association_keys = serializer.associations.map(&:key)

        assert expected_association_keys.include? :reviews
        assert expected_association_keys.include? :writer
        assert expected_association_keys.include? :site
      end

      class BelongsToBlogModel < ::Model
        attributes :id, :title
        associations :blog
      end
      class BelongsToBlogModelSerializer < ActiveModel::Serializer
        type :posts
        belongs_to :blog
      end

      def test_belongs_to_doesnt_load_record
        attributes = { id: 1, title: 'Belongs to Blog', blog: Blog.new(id: 5) }
        post = BelongsToBlogModel.new(attributes)
        class << post
          def blog
            fail 'should use blog_id'
          end

          def blog_id
            5
          end
        end

        actual =
          begin
            original_option = BelongsToBlogModelSerializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship
            BelongsToBlogModelSerializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship = true
            serializable(post, adapter: :json_api, serializer: BelongsToBlogModelSerializer).as_json
          ensure
            BelongsToBlogModelSerializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship = original_option
          end
        expected = { data: { id: '1', type: 'posts', relationships: { blog: { data: { id: '5', type: 'blogs' } } } } }

        assert_equal expected, actual
      end

      class ExternalBlog < Blog
        attributes :external_id
      end
      class BelongsToExternalBlogModel < ::Model
        attributes :id, :title, :external_blog_id
        associations :external_blog
      end
      class BelongsToExternalBlogModelSerializer < ActiveModel::Serializer
        type :posts
        belongs_to :external_blog

        def external_blog_id
          object.external_blog.external_id
        end
      end

      def test_belongs_to_allows_id_overwriting
        attributes = {
          id: 1,
          title: 'Title',
          external_blog: ExternalBlog.new(id: 5, external_id: 6)
        }
        post = BelongsToExternalBlogModel.new(attributes)

        actual =
          begin
            original_option = BelongsToExternalBlogModelSerializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship
            BelongsToExternalBlogModelSerializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship = true
            serializable(post, adapter: :json_api, serializer: BelongsToExternalBlogModelSerializer).as_json
          ensure
            BelongsToExternalBlogModelSerializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship = original_option
          end
        expected = { data: { id: '1', type: 'posts', relationships: { :'external-blog' => { data: { id: '6', type: 'external-blogs' } } } } }

        assert_equal expected, actual
      end

      class InlineAssociationTestPostSerializer < ActiveModel::Serializer
        has_many :comments
        has_many :comments, key: :last_comments do
          object.comments.last(1)
        end
      end

      def test_virtual_attribute_block
        comment1 = ::ARModels::Comment.create!(contents: 'first comment')
        comment2 = ::ARModels::Comment.create!(contents: 'last comment')
        post = ::ARModels::Post.create!(
          title: 'inline association test',
          body: 'etc',
          comments: [comment1, comment2]
        )
        actual = serializable(post, adapter: :attributes, serializer: InlineAssociationTestPostSerializer).as_json
        expected = {
          comments: [
            { id: 1, contents: 'first comment' },
            { id: 2, contents: 'last comment' }
          ],
          last_comments: [
            { id: 2, contents: 'last comment' }
          ]
        }

        assert_equal expected, actual
      ensure
        ::ARModels::Post.delete_all
        ::ARModels::Comment.delete_all
      end

      class NamespacedResourcesTest < ActiveSupport::TestCase
        class ResourceNamespace
          class Post < ::Model
            associations :comments, :author, :description
          end
          class Comment < ::Model; end
          class Author  < ::Model; end
          class Description < ::Model; end
          class PostSerializer < ActiveModel::Serializer
            has_many :comments
            belongs_to :author
            has_one :description
          end
          class CommentSerializer     < ActiveModel::Serializer; end
          class AuthorSerializer      < ActiveModel::Serializer; end
          class DescriptionSerializer < ActiveModel::Serializer; end
        end

        def setup
          @comment = ResourceNamespace::Comment.new
          @author = ResourceNamespace::Author.new
          @description = ResourceNamespace::Description.new
          @post = ResourceNamespace::Post.new(comments: [@comment],
                                              author: @author,
                                              description: @description)
          @post_serializer = ResourceNamespace::PostSerializer.new(@post)
        end

        def test_associations_namespaced_resources
          @post_serializer.associations.each do |association|
            case association.key
            when :comments
              assert_instance_of(ResourceNamespace::CommentSerializer, association.lazy_association.serializer.first)
            when :author
              assert_instance_of(ResourceNamespace::AuthorSerializer, association.lazy_association.serializer)
            when :description
              assert_instance_of(ResourceNamespace::DescriptionSerializer, association.lazy_association.serializer)
            else
              flunk "Unknown association: #{key}"
            end
          end
        end
      end

      class AssociationsNamespacedSerializersTest < ActiveSupport::TestCase
        class Post < ::Model
          associations :comments, :author, :description

          def latest_comments
            comments[0..3]
          end
        end
        class Comment < ::Model; end
        class Author  < ::Model; end
        class Description < ::Model; end

        class ResourceNamespace
          class PostSerializer < ActiveModel::Serializer
            has_many :comments, namespace: ResourceNamespace
            has_many :latest_comments, namespace: ResourceNamespace
            belongs_to :author, namespace: ResourceNamespace
            has_one :description, namespace: ResourceNamespace
          end
          class CommentSerializer     < ActiveModel::Serializer; end
          class AuthorSerializer      < ActiveModel::Serializer; end
          class DescriptionSerializer < ActiveModel::Serializer; end
        end

        def setup
          @comment = Comment.new
          @author = Author.new
          @description = Description.new
          @post = Post.new(comments: [@comment],
                           author: @author,
                           description: @description)
          @post_serializer = ResourceNamespace::PostSerializer.new(@post)
        end

        def test_associations_namespaced_serializers
          @post_serializer.associations.each do |association|
            case association.key
            when :comments, :latest_comments
              assert_instance_of(ResourceNamespace::CommentSerializer, association.lazy_association.serializer.first)
            when :author
              assert_instance_of(ResourceNamespace::AuthorSerializer, association.lazy_association.serializer)
            when :description
              assert_instance_of(ResourceNamespace::DescriptionSerializer, association.lazy_association.serializer)
            else
              flunk "Unknown association: #{key}"
            end
          end
        end
      end

      class NestedSerializersTest < ActiveSupport::TestCase
        class Post < ::Model
          associations :comments, :author, :description
        end
        class Comment < ::Model; end
        class Author  < ::Model; end
        class Description < ::Model; end
        class PostSerializer < ActiveModel::Serializer
          has_many :comments
          class CommentSerializer < ActiveModel::Serializer; end
          belongs_to :author
          class AuthorSerializer < ActiveModel::Serializer; end
          has_one :description
          class DescriptionSerializer < ActiveModel::Serializer; end
        end

        def setup
          @comment = Comment.new
          @author = Author.new
          @description = Description.new
          @post = Post.new(comments: [@comment],
                           author: @author,
                           description: @description)
          @post_serializer = PostSerializer.new(@post)
        end

        def test_associations_namespaced_resources
          @post_serializer.associations.each do |association|
            case association.key
            when :comments
              assert_instance_of(PostSerializer::CommentSerializer, association.lazy_association.serializer.first)
            when :author
              assert_instance_of(PostSerializer::AuthorSerializer, association.lazy_association.serializer)
            when :description
              assert_instance_of(PostSerializer::DescriptionSerializer, association.lazy_association.serializer)
            else
              flunk "Unknown association: #{key}"
            end
          end
        end

        # rubocop:disable Metrics/AbcSize
        def test_conditional_associations
          model = Class.new(::Model) do
            attributes :true, :false
            associations :something
          end.new(true: true, false: false)

          scenarios = [
            { options: { if:     :true  }, included: true  },
            { options: { if:     :false }, included: false },
            { options: { unless: :false }, included: true  },
            { options: { unless: :true  }, included: false },
            { options: { if:     'object.true'  }, included: true  },
            { options: { if:     'object.false' }, included: false },
            { options: { unless: 'object.false' }, included: true  },
            { options: { unless: 'object.true'  }, included: false },
            { options: { if:     -> { object.true }  }, included: true  },
            { options: { if:     -> { object.false } }, included: false },
            { options: { unless: -> { object.false } }, included: true  },
            { options: { unless: -> { object.true }  }, included: false },
            { options: { if:     -> (s) { s.object.true }  }, included: true  },
            { options: { if:     -> (s) { s.object.false } }, included: false },
            { options: { unless: -> (s) { s.object.false } }, included: true  },
            { options: { unless: -> (s) { s.object.true }  }, included: false }
          ]

          scenarios.each do |s|
            serializer = Class.new(ActiveModel::Serializer) do
              belongs_to :something, s[:options]

              def true
                true
              end

              def false
                false
              end
            end

            hash = serializable(model, serializer: serializer).serializable_hash
            assert_equal(s[:included], hash.key?(:something), "Error with #{s[:options]}")
          end
        end

        def test_illegal_conditional_associations
          exception = assert_raises(TypeError) do
            Class.new(ActiveModel::Serializer) do
              belongs_to :x, if: nil
            end
          end

          assert_match(/:if should be a Symbol, String or Proc/, exception.message)
        end
      end

      class InheritedSerializerTest < ActiveSupport::TestCase
        class PostSerializer < ActiveModel::Serializer
          belongs_to :author
          has_many :comments
          belongs_to :blog
        end

        class InheritedPostSerializer < PostSerializer
          belongs_to :author, polymorphic: true
          has_many :comments, key: :reviews
        end

        class AuthorSerializer < ActiveModel::Serializer
          has_many :posts
          has_many :roles
          has_one :bio
        end

        class InheritedAuthorSerializer < AuthorSerializer
          has_many :roles, polymorphic: true
          has_one :bio, polymorphic: true
        end

        def setup
          @author = Author.new(name: 'Steve K.')
          @post = Post.new(title: 'New Post', body: 'Body')
          @post_serializer = PostSerializer.new(@post)
          @author_serializer = AuthorSerializer.new(@author)
          @inherited_post_serializer = InheritedPostSerializer.new(@post)
          @inherited_author_serializer = InheritedAuthorSerializer.new(@author)
          @author_associations = @author_serializer.associations.to_a.sort_by(&:name)
          @inherited_author_associations = @inherited_author_serializer.associations.to_a.sort_by(&:name)
          @post_associations = @post_serializer.associations.to_a
          @inherited_post_associations = @inherited_post_serializer.associations.to_a
        end

        test 'an author serializer must have [posts,roles,bio] associations' do
          expected = [:posts, :roles, :bio].sort
          result = @author_serializer.associations.map(&:name).sort
          assert_equal(result, expected)
        end

        test 'a post serializer must have [author,comments,blog] associations' do
          expected = [:author, :comments, :blog].sort
          result = @post_serializer.associations.map(&:name).sort
          assert_equal(result, expected)
        end

        test 'a serializer inheriting from another serializer can redefine has_many and has_one associations' do
          expected = [:roles, :bio].sort
          result = (@inherited_author_associations.map(&:reflection) - @author_associations.map(&:reflection)).map(&:name)
          assert_equal(result, expected)
          assert_equal [true, false, true], @inherited_author_associations.map(&:polymorphic?)
          assert_equal [false, false, false], @author_associations.map(&:polymorphic?)
        end

        test 'a serializer inheriting from another serializer can redefine belongs_to associations' do
          assert_equal [:author, :comments, :blog], @post_associations.map(&:name)
          assert_equal [:author, :comments, :blog, :comments], @inherited_post_associations.map(&:name)

          refute @post_associations.detect { |assoc| assoc.name == :author }.polymorphic?
          assert @inherited_post_associations.detect { |assoc| assoc.name == :author }.polymorphic?

          refute @post_associations.detect { |assoc| assoc.name == :comments }.key?
          original_comment_assoc, new_comments_assoc = @inherited_post_associations.select { |assoc| assoc.name == :comments }
          refute original_comment_assoc.key?
          assert_equal :reviews, new_comments_assoc.key

          original_blog = @post_associations.detect { |assoc| assoc.name == :blog }
          inherited_blog = @inherited_post_associations.detect { |assoc| assoc.name == :blog }
          original_parent_serializer = original_blog.lazy_association.association_options.delete(:parent_serializer)
          inherited_parent_serializer = inherited_blog.lazy_association.association_options.delete(:parent_serializer)
          assert_equal PostSerializer, original_parent_serializer.class
          assert_equal InheritedPostSerializer, inherited_parent_serializer.class
        end

        test 'a serializer inheriting from another serializer can have an additional association with the same name but with different key' do
          expected = [:author, :comments, :blog, :reviews].sort
          result = @inherited_post_serializer.associations.map(&:key).sort
          assert_equal(result, expected)
        end
      end
    end
  end
end