active_model_serializers 0.10.0 → 0.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -4
  3. data/.travis.yml +9 -1
  4. data/CHANGELOG.md +81 -2
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +5 -2
  7. data/README.md +24 -24
  8. data/Rakefile +3 -3
  9. data/active_model_serializers.gemspec +20 -24
  10. data/docs/ARCHITECTURE.md +6 -7
  11. data/docs/README.md +2 -0
  12. data/docs/general/adapters.md +4 -2
  13. data/docs/general/caching.md +7 -1
  14. data/docs/general/configuration_options.md +70 -1
  15. data/docs/general/deserialization.md +1 -1
  16. data/docs/general/fields.md +31 -0
  17. data/docs/general/rendering.md +42 -3
  18. data/docs/general/serializers.md +97 -8
  19. data/docs/howto/add_pagination_links.md +4 -5
  20. data/docs/howto/add_relationship_links.md +137 -0
  21. data/docs/howto/add_root_key.md +4 -0
  22. data/docs/howto/grape_integration.md +42 -0
  23. data/docs/howto/outside_controller_use.md +9 -2
  24. data/docs/howto/passing_arbitrary_options.md +2 -2
  25. data/docs/howto/test.md +2 -0
  26. data/docs/howto/upgrade_from_0_8_to_0_10.md +265 -0
  27. data/docs/integrations/ember-and-json-api.md +64 -32
  28. data/docs/jsonapi/schema.md +1 -1
  29. data/lib/action_controller/serialization.rb +13 -3
  30. data/lib/active_model/serializer/adapter/base.rb +2 -0
  31. data/lib/active_model/serializer/array_serializer.rb +8 -5
  32. data/lib/active_model/serializer/association.rb +19 -4
  33. data/lib/active_model/serializer/belongs_to_reflection.rb +0 -3
  34. data/lib/active_model/serializer/collection_serializer.rb +35 -12
  35. data/lib/active_model/serializer/{associations.rb → concerns/associations.rb} +13 -11
  36. data/lib/active_model/serializer/{attributes.rb → concerns/attributes.rb} +0 -0
  37. data/lib/active_model/serializer/{caching.rb → concerns/caching.rb} +72 -113
  38. data/lib/active_model/serializer/{configuration.rb → concerns/configuration.rb} +25 -1
  39. data/lib/active_model/serializer/{links.rb → concerns/links.rb} +0 -0
  40. data/lib/active_model/serializer/{meta.rb → concerns/meta.rb} +0 -0
  41. data/lib/active_model/serializer/{type.rb → concerns/type.rb} +0 -0
  42. data/lib/active_model/serializer/error_serializer.rb +11 -7
  43. data/lib/active_model/serializer/errors_serializer.rb +25 -20
  44. data/lib/active_model/serializer/has_many_reflection.rb +0 -3
  45. data/lib/active_model/serializer/has_one_reflection.rb +0 -3
  46. data/lib/active_model/serializer/lint.rb +134 -130
  47. data/lib/active_model/serializer/reflection.rb +37 -21
  48. data/lib/active_model/serializer/version.rb +1 -1
  49. data/lib/active_model/serializer.rb +76 -37
  50. data/lib/active_model_serializers/adapter/attributes.rb +3 -66
  51. data/lib/active_model_serializers/adapter/base.rb +38 -38
  52. data/lib/active_model_serializers/adapter/json_api/link.rb +1 -1
  53. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +8 -1
  54. data/lib/active_model_serializers/adapter/json_api/relationship.rb +30 -19
  55. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +23 -9
  56. data/lib/active_model_serializers/adapter/json_api.rb +44 -43
  57. data/lib/active_model_serializers/adapter.rb +6 -0
  58. data/lib/active_model_serializers/deprecate.rb +1 -2
  59. data/lib/active_model_serializers/deserialization.rb +2 -0
  60. data/lib/active_model_serializers/key_transform.rb +4 -0
  61. data/lib/active_model_serializers/lookup_chain.rb +80 -0
  62. data/lib/active_model_serializers/model.rb +4 -2
  63. data/lib/active_model_serializers/railtie.rb +3 -1
  64. data/lib/active_model_serializers/register_jsonapi_renderer.rb +44 -31
  65. data/lib/active_model_serializers/serializable_resource.rb +6 -5
  66. data/lib/active_model_serializers/serialization_context.rb +10 -3
  67. data/lib/active_model_serializers.rb +7 -0
  68. data/lib/generators/rails/serializer_generator.rb +4 -4
  69. data/lib/grape/active_model_serializers.rb +7 -5
  70. data/lib/grape/formatters/active_model_serializers.rb +19 -2
  71. data/lib/grape/helpers/active_model_serializers.rb +1 -0
  72. data/test/action_controller/adapter_selector_test.rb +4 -4
  73. data/test/action_controller/explicit_serializer_test.rb +5 -4
  74. data/test/action_controller/json/include_test.rb +106 -27
  75. data/test/action_controller/json_api/errors_test.rb +6 -7
  76. data/test/action_controller/json_api/fields_test.rb +57 -0
  77. data/test/action_controller/json_api/linked_test.rb +29 -24
  78. data/test/action_controller/json_api/pagination_test.rb +19 -19
  79. data/test/action_controller/json_api/transform_test.rb +3 -3
  80. data/test/action_controller/lookup_proc_test.rb +49 -0
  81. data/test/action_controller/namespace_lookup_test.rb +226 -0
  82. data/test/action_controller/serialization_test.rb +10 -7
  83. data/test/active_model_serializers/json_pointer_test.rb +15 -13
  84. data/test/active_model_serializers/key_transform_test.rb +286 -252
  85. data/test/active_model_serializers/model_test.rb +17 -4
  86. data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +143 -0
  87. data/test/active_model_serializers/serialization_context_test_isolated.rb +23 -10
  88. data/test/adapter/attributes_test.rb +43 -0
  89. data/test/adapter/json/collection_test.rb +14 -0
  90. data/test/adapter/json/transform_test.rb +15 -15
  91. data/test/adapter/json_api/collection_test.rb +4 -3
  92. data/test/adapter/json_api/errors_test.rb +17 -19
  93. data/test/adapter/json_api/fields_test.rb +4 -3
  94. data/test/adapter/json_api/has_many_test.rb +39 -18
  95. data/test/adapter/json_api/include_data_if_sideloaded_test.rb +166 -0
  96. data/test/adapter/json_api/json_api_test.rb +5 -7
  97. data/test/adapter/json_api/linked_test.rb +33 -12
  98. data/test/adapter/json_api/links_test.rb +4 -2
  99. data/test/adapter/json_api/pagination_links_test.rb +35 -8
  100. data/test/adapter/json_api/relationship_test.rb +309 -73
  101. data/test/adapter/json_api/resource_identifier_test.rb +27 -2
  102. data/test/adapter/json_api/resource_meta_test.rb +3 -3
  103. data/test/adapter/json_api/transform_test.rb +255 -253
  104. data/test/adapter/json_api/type_test.rb +1 -1
  105. data/test/adapter/json_test.rb +8 -7
  106. data/test/adapter/null_test.rb +1 -2
  107. data/test/adapter/polymorphic_test.rb +5 -5
  108. data/test/adapter_test.rb +1 -1
  109. data/test/benchmark/app.rb +1 -1
  110. data/test/benchmark/benchmarking_support.rb +1 -1
  111. data/test/benchmark/bm_active_record.rb +81 -0
  112. data/test/benchmark/bm_adapter.rb +38 -0
  113. data/test/benchmark/bm_caching.rb +16 -16
  114. data/test/benchmark/bm_lookup_chain.rb +83 -0
  115. data/test/benchmark/bm_transform.rb +16 -5
  116. data/test/benchmark/controllers.rb +16 -17
  117. data/test/benchmark/fixtures.rb +72 -72
  118. data/test/cache_test.rb +143 -49
  119. data/test/collection_serializer_test.rb +3 -3
  120. data/test/fixtures/poro.rb +52 -48
  121. data/test/generators/serializer_generator_test.rb +22 -5
  122. data/test/grape_test.rb +152 -56
  123. data/test/lint_test.rb +1 -1
  124. data/test/logger_test.rb +13 -11
  125. data/test/serializable_resource_test.rb +18 -22
  126. data/test/serializers/association_macros_test.rb +3 -2
  127. data/test/serializers/associations_test.rb +107 -32
  128. data/test/serializers/attribute_test.rb +2 -2
  129. data/test/serializers/attributes_test.rb +1 -1
  130. data/test/serializers/fieldset_test.rb +1 -1
  131. data/test/serializers/meta_test.rb +12 -6
  132. data/test/serializers/root_test.rb +1 -1
  133. data/test/serializers/serializer_for_test.rb +6 -4
  134. data/test/serializers/serializer_for_with_namespace_test.rb +87 -0
  135. data/test/support/isolated_unit.rb +5 -2
  136. data/test/support/rails5_shims.rb +8 -2
  137. data/test/support/rails_app.rb +0 -9
  138. data/test/support/serialization_testing.rb +23 -5
  139. data/test/test_helper.rb +1 -0
  140. metadata +85 -34
  141. data/.rubocop_todo.yml +0 -167
  142. data/lib/active_model/serializer/include_tree.rb +0 -111
  143. data/test/adapter/json_api/relationships_test.rb +0 -199
  144. data/test/include_tree/from_include_args_test.rb +0 -26
  145. data/test/include_tree/from_string_test.rb +0 -94
  146. data/test/include_tree/include_args_to_hash_test.rb +0 -64
@@ -1,146 +1,150 @@
1
- module ActiveModel::Serializer::Lint
2
- # == Active \Model \Serializer \Lint \Tests
3
- #
4
- # You can test whether an object is compliant with the Active \Model \Serializers
5
- # API by including <tt>ActiveModel::Serializer::Lint::Tests</tt> in your TestCase.
6
- # It will include tests that tell you whether your object is fully compliant,
7
- # or if not, which aspects of the API are not implemented.
8
- #
9
- # Note an object is not required to implement all APIs in order to work
10
- # with Active \Model \Serializers. This module only intends to provide guidance in case
11
- # you want all features out of the box.
12
- #
13
- # These tests do not attempt to determine the semantic correctness of the
14
- # returned values. For instance, you could implement <tt>serializable_hash</tt> to
15
- # always return +{}+, and the tests would pass. It is up to you to ensure
16
- # that the values are semantically meaningful.
17
- module Tests
18
- # Passes if the object responds to <tt>serializable_hash</tt> and if it takes
19
- # zero or one arguments.
20
- # Fails otherwise.
21
- #
22
- # <tt>serializable_hash</tt> returns a hash representation of a object's attributes.
23
- # Typically, it is implemented by including ActiveModel::Serialization.
24
- def test_serializable_hash
25
- assert_respond_to resource, :serializable_hash, 'The resource should respond to serializable_hash'
26
- resource.serializable_hash
27
- resource.serializable_hash(nil)
28
- end
1
+ module ActiveModel
2
+ class Serializer
3
+ module Lint
4
+ # == Active \Model \Serializer \Lint \Tests
5
+ #
6
+ # You can test whether an object is compliant with the Active \Model \Serializers
7
+ # API by including <tt>ActiveModel::Serializer::Lint::Tests</tt> in your TestCase.
8
+ # It will include tests that tell you whether your object is fully compliant,
9
+ # or if not, which aspects of the API are not implemented.
10
+ #
11
+ # Note an object is not required to implement all APIs in order to work
12
+ # with Active \Model \Serializers. This module only intends to provide guidance in case
13
+ # you want all features out of the box.
14
+ #
15
+ # These tests do not attempt to determine the semantic correctness of the
16
+ # returned values. For instance, you could implement <tt>serializable_hash</tt> to
17
+ # always return +{}+, and the tests would pass. It is up to you to ensure
18
+ # that the values are semantically meaningful.
19
+ module Tests
20
+ # Passes if the object responds to <tt>serializable_hash</tt> and if it takes
21
+ # zero or one arguments.
22
+ # Fails otherwise.
23
+ #
24
+ # <tt>serializable_hash</tt> returns a hash representation of a object's attributes.
25
+ # Typically, it is implemented by including ActiveModel::Serialization.
26
+ def test_serializable_hash
27
+ assert_respond_to resource, :serializable_hash, 'The resource should respond to serializable_hash'
28
+ resource.serializable_hash
29
+ resource.serializable_hash(nil)
30
+ end
29
31
 
30
- # Passes if the object responds to <tt>read_attribute_for_serialization</tt>
31
- # and if it requires one argument (the attribute to be read).
32
- # Fails otherwise.
33
- #
34
- # <tt>read_attribute_for_serialization</tt> gets the attribute value for serialization
35
- # Typically, it is implemented by including ActiveModel::Serialization.
36
- def test_read_attribute_for_serialization
37
- assert_respond_to resource, :read_attribute_for_serialization, 'The resource should respond to read_attribute_for_serialization'
38
- actual_arity = resource.method(:read_attribute_for_serialization).arity
39
- # using absolute value since arity is:
40
- # 1 for def read_attribute_for_serialization(name); end
41
- # -1 for alias :read_attribute_for_serialization :send
42
- assert_equal 1, actual_arity.abs, "expected #{actual_arity.inspect}.abs to be 1 or -1"
43
- end
32
+ # Passes if the object responds to <tt>read_attribute_for_serialization</tt>
33
+ # and if it requires one argument (the attribute to be read).
34
+ # Fails otherwise.
35
+ #
36
+ # <tt>read_attribute_for_serialization</tt> gets the attribute value for serialization
37
+ # Typically, it is implemented by including ActiveModel::Serialization.
38
+ def test_read_attribute_for_serialization
39
+ assert_respond_to resource, :read_attribute_for_serialization, 'The resource should respond to read_attribute_for_serialization'
40
+ actual_arity = resource.method(:read_attribute_for_serialization).arity
41
+ # using absolute value since arity is:
42
+ # 1 for def read_attribute_for_serialization(name); end
43
+ # -1 for alias :read_attribute_for_serialization :send
44
+ assert_equal 1, actual_arity.abs, "expected #{actual_arity.inspect}.abs to be 1 or -1"
45
+ end
44
46
 
45
- # Passes if the object responds to <tt>as_json</tt> and if it takes
46
- # zero or one arguments.
47
- # Fails otherwise.
48
- #
49
- # <tt>as_json</tt> returns a hash representation of a serialized object.
50
- # It may delegate to <tt>serializable_hash</tt>
51
- # Typically, it is implemented either by including ActiveModel::Serialization
52
- # which includes ActiveModel::Serializers::JSON.
53
- # or by the JSON gem when required.
54
- def test_as_json
55
- assert_respond_to resource, :as_json
56
- resource.as_json
57
- resource.as_json(nil)
58
- end
47
+ # Passes if the object responds to <tt>as_json</tt> and if it takes
48
+ # zero or one arguments.
49
+ # Fails otherwise.
50
+ #
51
+ # <tt>as_json</tt> returns a hash representation of a serialized object.
52
+ # It may delegate to <tt>serializable_hash</tt>
53
+ # Typically, it is implemented either by including ActiveModel::Serialization
54
+ # which includes ActiveModel::Serializers::JSON.
55
+ # or by the JSON gem when required.
56
+ def test_as_json
57
+ assert_respond_to resource, :as_json
58
+ resource.as_json
59
+ resource.as_json(nil)
60
+ end
59
61
 
60
- # Passes if the object responds to <tt>to_json</tt> and if it takes
61
- # zero or one arguments.
62
- # Fails otherwise.
63
- #
64
- # <tt>to_json</tt> returns a string representation (JSON) of a serialized object.
65
- # It may be called on the result of <tt>as_json</tt>.
66
- # Typically, it is implemented on all objects when the JSON gem is required.
67
- def test_to_json
68
- assert_respond_to resource, :to_json
69
- resource.to_json
70
- resource.to_json(nil)
71
- end
62
+ # Passes if the object responds to <tt>to_json</tt> and if it takes
63
+ # zero or one arguments.
64
+ # Fails otherwise.
65
+ #
66
+ # <tt>to_json</tt> returns a string representation (JSON) of a serialized object.
67
+ # It may be called on the result of <tt>as_json</tt>.
68
+ # Typically, it is implemented on all objects when the JSON gem is required.
69
+ def test_to_json
70
+ assert_respond_to resource, :to_json
71
+ resource.to_json
72
+ resource.to_json(nil)
73
+ end
72
74
 
73
- # Passes if the object responds to <tt>cache_key</tt>
74
- # Fails otherwise.
75
- #
76
- # <tt>cache_key</tt> returns a (self-expiring) unique key for the object,
77
- # and is part of the (self-expiring) cache_key, which is used by the
78
- # adapter. It is not required unless caching is enabled.
79
- def test_cache_key
80
- assert_respond_to resource, :cache_key
81
- actual_arity = resource.method(:cache_key).arity
82
- assert_includes [-1, 0], actual_arity, "expected #{actual_arity.inspect} to be 0 or -1"
83
- end
75
+ # Passes if the object responds to <tt>cache_key</tt>
76
+ # Fails otherwise.
77
+ #
78
+ # <tt>cache_key</tt> returns a (self-expiring) unique key for the object,
79
+ # and is part of the (self-expiring) cache_key, which is used by the
80
+ # adapter. It is not required unless caching is enabled.
81
+ def test_cache_key
82
+ assert_respond_to resource, :cache_key
83
+ actual_arity = resource.method(:cache_key).arity
84
+ assert_includes [-1, 0], actual_arity, "expected #{actual_arity.inspect} to be 0 or -1"
85
+ end
84
86
 
85
- # Passes if the object responds to <tt>updated_at</tt> and if it takes no
86
- # arguments.
87
- # Fails otherwise.
88
- #
89
- # <tt>updated_at</tt> returns a Time object or iso8601 string and
90
- # is part of the (self-expiring) cache_key, which is used by the adapter.
91
- # It is not required unless caching is enabled.
92
- def test_updated_at
93
- assert_respond_to resource, :updated_at
94
- actual_arity = resource.method(:updated_at).arity
95
- assert_equal 0, actual_arity
96
- end
87
+ # Passes if the object responds to <tt>updated_at</tt> and if it takes no
88
+ # arguments.
89
+ # Fails otherwise.
90
+ #
91
+ # <tt>updated_at</tt> returns a Time object or iso8601 string and
92
+ # is part of the (self-expiring) cache_key, which is used by the adapter.
93
+ # It is not required unless caching is enabled.
94
+ def test_updated_at
95
+ assert_respond_to resource, :updated_at
96
+ actual_arity = resource.method(:updated_at).arity
97
+ assert_equal 0, actual_arity
98
+ end
97
99
 
98
- # Passes if the object responds to <tt>id</tt> and if it takes no
99
- # arguments.
100
- # Fails otherwise.
101
- #
102
- # <tt>id</tt> returns a unique identifier for the object.
103
- # It is not required unless caching is enabled.
104
- def test_id
105
- assert_respond_to resource, :id
106
- assert_equal 0, resource.method(:id).arity
107
- end
100
+ # Passes if the object responds to <tt>id</tt> and if it takes no
101
+ # arguments.
102
+ # Fails otherwise.
103
+ #
104
+ # <tt>id</tt> returns a unique identifier for the object.
105
+ # It is not required unless caching is enabled.
106
+ def test_id
107
+ assert_respond_to resource, :id
108
+ assert_equal 0, resource.method(:id).arity
109
+ end
108
110
 
109
- # Passes if the object's class responds to <tt>model_name</tt> and if it
110
- # is in an instance of +ActiveModel::Name+.
111
- # Fails otherwise.
112
- #
113
- # <tt>model_name</tt> returns an ActiveModel::Name instance.
114
- # It is used by the serializer to identify the object's type.
115
- # It is not required unless caching is enabled.
116
- def test_model_name
117
- resource_class = resource.class
118
- assert_respond_to resource_class, :model_name
119
- assert_instance_of resource_class.model_name, ActiveModel::Name
120
- end
111
+ # Passes if the object's class responds to <tt>model_name</tt> and if it
112
+ # is in an instance of +ActiveModel::Name+.
113
+ # Fails otherwise.
114
+ #
115
+ # <tt>model_name</tt> returns an ActiveModel::Name instance.
116
+ # It is used by the serializer to identify the object's type.
117
+ # It is not required unless caching is enabled.
118
+ def test_model_name
119
+ resource_class = resource.class
120
+ assert_respond_to resource_class, :model_name
121
+ assert_instance_of resource_class.model_name, ActiveModel::Name
122
+ end
121
123
 
122
- def test_active_model_errors
123
- assert_respond_to resource, :errors
124
- end
124
+ def test_active_model_errors
125
+ assert_respond_to resource, :errors
126
+ end
125
127
 
126
- def test_active_model_errors_human_attribute_name
127
- assert_respond_to resource.class, :human_attribute_name
128
- assert_equal(-2, resource.class.method(:human_attribute_name).arity)
129
- end
128
+ def test_active_model_errors_human_attribute_name
129
+ assert_respond_to resource.class, :human_attribute_name
130
+ assert_equal(-2, resource.class.method(:human_attribute_name).arity)
131
+ end
130
132
 
131
- def test_active_model_errors_lookup_ancestors
132
- assert_respond_to resource.class, :lookup_ancestors
133
- assert_equal 0, resource.class.method(:lookup_ancestors).arity
134
- end
133
+ def test_active_model_errors_lookup_ancestors
134
+ assert_respond_to resource.class, :lookup_ancestors
135
+ assert_equal 0, resource.class.method(:lookup_ancestors).arity
136
+ end
135
137
 
136
- private
138
+ private
137
139
 
138
- def resource
139
- @resource or fail "'@resource' must be set as the linted object"
140
- end
140
+ def resource
141
+ @resource or fail "'@resource' must be set as the linted object"
142
+ end
141
143
 
142
- def assert_instance_of(result, name)
143
- assert result.instance_of?(name), "#{result} should be an instance of #{name}"
144
+ def assert_instance_of(result, name)
145
+ assert result.instance_of?(name), "#{result} should be an instance of #{name}"
146
+ end
147
+ end
144
148
  end
145
149
  end
146
150
  end
@@ -37,7 +37,7 @@ module ActiveModel
37
37
  def initialize(*)
38
38
  super
39
39
  @_links = {}
40
- @_include_data = true
40
+ @_include_data = Serializer.config.include_data_default
41
41
  @_meta = nil
42
42
  end
43
43
 
@@ -69,17 +69,15 @@ module ActiveModel
69
69
  # Blog.find(object.blog_id)
70
70
  # end
71
71
  # end
72
- def value(serializer)
72
+ def value(serializer, include_slice)
73
73
  @object = serializer.object
74
74
  @scope = serializer.scope
75
75
 
76
- if block
77
- block_value = instance_exec(serializer, &block)
78
- if block_value == :nil
79
- serializer.read_attribute_for_serialization(name)
80
- else
81
- block_value
82
- end
76
+ block_value = instance_exec(serializer, &block) if block
77
+ return unless include_data?(include_slice)
78
+
79
+ if block && block_value != :nil
80
+ block_value
83
81
  else
84
82
  serializer.read_attribute_for_serialization(name)
85
83
  end
@@ -88,7 +86,7 @@ module ActiveModel
88
86
  # Build association. This method is used internally to
89
87
  # build serializer's association by its reflection.
90
88
  #
91
- # @param [Serializer] subject is a parent serializer for given association
89
+ # @param [Serializer] parent_serializer for given association
92
90
  # @param [Hash{Symbol => Object}] parent_serializer_options
93
91
  #
94
92
  # @example
@@ -106,26 +104,36 @@ module ActiveModel
106
104
  #
107
105
  # @api private
108
106
  #
109
- def build_association(subject, parent_serializer_options)
110
- association_value = value(subject)
107
+ def build_association(parent_serializer, parent_serializer_options, include_slice = {})
111
108
  reflection_options = options.dup
112
- serializer_class = subject.class.serializer_for(association_value, reflection_options)
113
- reflection_options[:include_data] = @_include_data
109
+
110
+ # Pass the parent's namespace onto the child serializer
111
+ reflection_options[:namespace] ||= parent_serializer_options[:namespace]
112
+
113
+ association_value = value(parent_serializer, include_slice)
114
+ serializer_class = parent_serializer.class.serializer_for(association_value, reflection_options)
115
+ reflection_options[:include_data] = include_data?(include_slice)
116
+ reflection_options[:links] = @_links
117
+ reflection_options[:meta] = @_meta
114
118
 
115
119
  if serializer_class
116
- begin
117
- serializer = serializer_class.new(
120
+ serializer = catch(:no_serializer) do
121
+ serializer_class.new(
118
122
  association_value,
119
- serializer_options(subject, parent_serializer_options, reflection_options)
123
+ serializer_options(parent_serializer, parent_serializer_options, reflection_options)
120
124
  )
121
- rescue ActiveModel::Serializer::CollectionSerializer::NoSerializerError
125
+ end
126
+ if serializer.nil?
122
127
  reflection_options[:virtual_value] = association_value.try(:as_json) || association_value
128
+ else
129
+ reflection_options[:serializer] = serializer
123
130
  end
124
131
  elsif !association_value.nil? && !association_value.instance_of?(Object)
125
132
  reflection_options[:virtual_value] = association_value
126
133
  end
127
134
 
128
- Association.new(name, serializer, reflection_options, @_links, @_meta)
135
+ block = nil
136
+ Association.new(name, reflection_options, block)
129
137
  end
130
138
 
131
139
  protected
@@ -134,12 +142,20 @@ module ActiveModel
134
142
 
135
143
  private
136
144
 
137
- def serializer_options(subject, parent_serializer_options, reflection_options)
145
+ def include_data?(include_slice)
146
+ if @_include_data == :if_sideloaded
147
+ include_slice.key?(name)
148
+ else
149
+ @_include_data
150
+ end
151
+ end
152
+
153
+ def serializer_options(parent_serializer, parent_serializer_options, reflection_options)
138
154
  serializer = reflection_options.fetch(:serializer, nil)
139
155
 
140
156
  serializer_options = parent_serializer_options.except(:serializer)
141
157
  serializer_options[:serializer] = serializer if serializer
142
- serializer_options[:serializer_context_class] = subject.class
158
+ serializer_options[:serializer_context_class] = parent_serializer.class
143
159
  serializer_options
144
160
  end
145
161
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveModel
2
2
  class Serializer
3
- VERSION = '0.10.0'.freeze
3
+ VERSION = '0.10.3'.freeze
4
4
  end
5
5
  end
@@ -1,18 +1,18 @@
1
1
  require 'thread_safe'
2
+ require 'jsonapi/include_directive'
2
3
  require 'active_model/serializer/collection_serializer'
3
4
  require 'active_model/serializer/array_serializer'
4
5
  require 'active_model/serializer/error_serializer'
5
6
  require 'active_model/serializer/errors_serializer'
6
- require 'active_model/serializer/include_tree'
7
- require 'active_model/serializer/associations'
8
- require 'active_model/serializer/attributes'
9
- require 'active_model/serializer/caching'
10
- require 'active_model/serializer/configuration'
7
+ require 'active_model/serializer/concerns/associations'
8
+ require 'active_model/serializer/concerns/attributes'
9
+ require 'active_model/serializer/concerns/caching'
10
+ require 'active_model/serializer/concerns/configuration'
11
+ require 'active_model/serializer/concerns/links'
12
+ require 'active_model/serializer/concerns/meta'
13
+ require 'active_model/serializer/concerns/type'
11
14
  require 'active_model/serializer/fieldset'
12
15
  require 'active_model/serializer/lint'
13
- require 'active_model/serializer/links'
14
- require 'active_model/serializer/meta'
15
- require 'active_model/serializer/type'
16
16
 
17
17
  # ActiveModel::Serializer is an abstract class that is
18
18
  # reified when subclassed to decorate a resource.
@@ -44,7 +44,7 @@ module ActiveModel
44
44
  elsif resource.respond_to?(:to_ary)
45
45
  config.collection_serializer
46
46
  else
47
- options.fetch(:serializer) { get_serializer_for(resource.class) }
47
+ options.fetch(:serializer) { get_serializer_for(resource.class, options[:namespace]) }
48
48
  end
49
49
  end
50
50
 
@@ -59,17 +59,11 @@ module ActiveModel
59
59
  end
60
60
 
61
61
  # @api private
62
- def self.serializer_lookup_chain_for(klass)
63
- chain = []
64
-
65
- resource_class_name = klass.name.demodulize
66
- resource_namespace = klass.name.deconstantize
67
- serializer_class_name = "#{resource_class_name}Serializer"
68
-
69
- chain.push("#{name}::#{serializer_class_name}") if self != ActiveModel::Serializer
70
- chain.push("#{resource_namespace}::#{serializer_class_name}")
71
-
72
- chain
62
+ def self.serializer_lookup_chain_for(klass, namespace = nil)
63
+ lookups = ActiveModelSerializers.config.serializer_lookup_chain
64
+ Array[*lookups].flat_map do |lookup|
65
+ lookup.call(klass, self, namespace)
66
+ end.compact
73
67
  end
74
68
 
75
69
  # Used to cache serializer name => serializer class
@@ -84,11 +78,14 @@ module ActiveModel
84
78
  # 1. class name appended with "Serializer"
85
79
  # 2. try again with superclass, if present
86
80
  # 3. nil
87
- def self.get_serializer_for(klass)
81
+ def self.get_serializer_for(klass, namespace = nil)
88
82
  return nil unless config.serializer_lookup_enabled
89
- serializers_cache.fetch_or_store(klass) do
83
+
84
+ cache_key = ActiveSupport::Cache.expand_cache_key(klass, namespace)
85
+ serializers_cache.fetch_or_store(cache_key) do
90
86
  # NOTE(beauby): When we drop 1.9.3 support we can lazify the map for perfs.
91
- serializer_class = serializer_lookup_chain_for(klass).map(&:safe_constantize).find { |x| x && x < ActiveModel::Serializer }
87
+ lookup_chain = serializer_lookup_chain_for(klass, namespace)
88
+ serializer_class = lookup_chain.map(&:safe_constantize).find { |x| x && x < ActiveModel::Serializer }
92
89
 
93
90
  if serializer_class
94
91
  serializer_class
@@ -98,6 +95,22 @@ module ActiveModel
98
95
  end
99
96
  end
100
97
 
98
+ # @api private
99
+ def self.include_directive_from_options(options)
100
+ if options[:include_directive]
101
+ options[:include_directive]
102
+ elsif options[:include]
103
+ JSONAPI::IncludeDirective.new(options[:include], allow_wildcard: true)
104
+ else
105
+ ActiveModelSerializers.default_include_directive
106
+ end
107
+ end
108
+
109
+ # @api private
110
+ def self.serialization_adapter_instance
111
+ @serialization_adapter_instance ||= ActiveModelSerializers::Adapter::Attributes
112
+ end
113
+
101
114
  attr_accessor :object, :root, :scope
102
115
 
103
116
  # `scope_name` is set as :current_user by default in the controller.
@@ -109,10 +122,9 @@ module ActiveModel
109
122
  self.root = instance_options[:root]
110
123
  self.scope = instance_options[:scope]
111
124
 
112
- scope_name = instance_options[:scope_name]
113
- if scope_name && !respond_to?(scope_name)
114
- define_singleton_method scope_name, lambda { scope }
115
- end
125
+ return if !(scope_name = instance_options[:scope_name]) || respond_to?(scope_name)
126
+
127
+ define_singleton_method scope_name, -> { scope }
116
128
  end
117
129
 
118
130
  def success?
@@ -123,9 +135,7 @@ module ActiveModel
123
135
  # associations, similar to how ActiveModel::Serializers::JSON is used
124
136
  # in ActiveRecord::Base.
125
137
  #
126
- # TODO: Move to here the Attributes adapter logic for
127
- # +serializable_hash_for_single_resource(options)+
128
- # and include <tt>ActiveModel::Serializers::JSON</tt>.
138
+ # TODO: Include <tt>ActiveModel::Serializers::JSON</tt>.
129
139
  # So that the below is true:
130
140
  # @param options [nil, Hash] The same valid options passed to `serializable_hash`
131
141
  # (:only, :except, :methods, and :include).
@@ -149,11 +159,13 @@ module ActiveModel
149
159
  # serializer.as_json(include: :posts)
150
160
  # # Second level and higher order associations work as well:
151
161
  # serializer.as_json(include: { posts: { include: { comments: { only: :body } }, only: :title } })
152
- def serializable_hash(adapter_opts = nil)
153
- adapter_opts ||= {}
154
- adapter_opts = { include: '*', adapter: :attributes }.merge!(adapter_opts)
155
- adapter = ActiveModelSerializers::Adapter.create(self, adapter_opts)
156
- adapter.serializable_hash(adapter_opts)
162
+ def serializable_hash(adapter_options = nil, options = {}, adapter_instance = self.class.serialization_adapter_instance)
163
+ adapter_options ||= {}
164
+ options[:include_directive] ||= ActiveModel::Serializer.include_directive_from_options(adapter_options)
165
+ cached_attributes = adapter_options[:cached_attributes] ||= {}
166
+ resource = fetch_attributes(options[:fields], cached_attributes, adapter_instance)
167
+ relationships = resource_relationships(adapter_options, options, adapter_instance)
168
+ resource.merge(relationships)
157
169
  end
158
170
  alias to_hash serializable_hash
159
171
  alias to_h serializable_hash
@@ -178,13 +190,40 @@ module ActiveModel
178
190
  def read_attribute_for_serialization(attr)
179
191
  if respond_to?(attr)
180
192
  send(attr)
181
- elsif self.class._fragmented
182
- self.class._fragmented.read_attribute_for_serialization(attr)
183
193
  else
184
194
  object.read_attribute_for_serialization(attr)
185
195
  end
186
196
  end
187
197
 
198
+ # @api private
199
+ def resource_relationships(adapter_options, options, adapter_instance)
200
+ relationships = {}
201
+ include_directive = options.fetch(:include_directive)
202
+ associations(include_directive).each do |association|
203
+ adapter_opts = adapter_options.merge(include_directive: include_directive[association.key])
204
+ relationships[association.key] ||= relationship_value_for(association, adapter_opts, adapter_instance)
205
+ end
206
+
207
+ relationships
208
+ end
209
+
210
+ # @api private
211
+ def relationship_value_for(association, adapter_options, adapter_instance)
212
+ return association.options[:virtual_value] if association.options[:virtual_value]
213
+ association_serializer = association.serializer
214
+ association_object = association_serializer && association_serializer.object
215
+ return unless association_object
216
+
217
+ relationship_value = association_serializer.serializable_hash(adapter_options, {}, adapter_instance)
218
+
219
+ if association.options[:polymorphic] && relationship_value
220
+ polymorphic_type = association_object.class.name.underscore
221
+ relationship_value = { type: polymorphic_type, polymorphic_type.to_sym => relationship_value }
222
+ end
223
+
224
+ relationship_value
225
+ end
226
+
188
227
  protected
189
228
 
190
229
  attr_accessor :instance_options