active_model_serializers 0.10.0 → 0.10.9

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 (206) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +10 -5
  3. data/.travis.yml +41 -21
  4. data/CHANGELOG.md +200 -2
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +25 -4
  7. data/README.md +166 -28
  8. data/Rakefile +5 -32
  9. data/active_model_serializers.gemspec +23 -25
  10. data/appveyor.yml +10 -6
  11. data/bin/rubocop +38 -0
  12. data/docs/README.md +2 -1
  13. data/docs/general/adapters.md +35 -11
  14. data/docs/general/caching.md +7 -1
  15. data/docs/general/configuration_options.md +86 -1
  16. data/docs/general/deserialization.md +1 -1
  17. data/docs/general/fields.md +31 -0
  18. data/docs/general/getting_started.md +1 -1
  19. data/docs/general/logging.md +7 -0
  20. data/docs/general/rendering.md +63 -25
  21. data/docs/general/serializers.md +137 -14
  22. data/docs/howto/add_pagination_links.md +16 -17
  23. data/docs/howto/add_relationship_links.md +140 -0
  24. data/docs/howto/add_root_key.md +11 -0
  25. data/docs/howto/grape_integration.md +42 -0
  26. data/docs/howto/outside_controller_use.md +12 -4
  27. data/docs/howto/passing_arbitrary_options.md +2 -2
  28. data/docs/howto/serialize_poro.md +46 -5
  29. data/docs/howto/test.md +2 -0
  30. data/docs/howto/upgrade_from_0_8_to_0_10.md +265 -0
  31. data/docs/integrations/ember-and-json-api.md +67 -32
  32. data/docs/jsonapi/schema.md +1 -1
  33. data/lib/action_controller/serialization.rb +15 -3
  34. data/lib/active_model/serializable_resource.rb +2 -0
  35. data/lib/active_model/serializer/adapter/attributes.rb +2 -0
  36. data/lib/active_model/serializer/adapter/base.rb +4 -0
  37. data/lib/active_model/serializer/adapter/json.rb +2 -0
  38. data/lib/active_model/serializer/adapter/json_api.rb +2 -0
  39. data/lib/active_model/serializer/adapter/null.rb +2 -0
  40. data/lib/active_model/serializer/adapter.rb +2 -0
  41. data/lib/active_model/serializer/array_serializer.rb +10 -5
  42. data/lib/active_model/serializer/association.rb +64 -10
  43. data/lib/active_model/serializer/attribute.rb +2 -0
  44. data/lib/active_model/serializer/belongs_to_reflection.rb +6 -3
  45. data/lib/active_model/serializer/collection_serializer.rb +39 -13
  46. data/lib/active_model/serializer/{caching.rb → concerns/caching.rb} +87 -116
  47. data/lib/active_model/serializer/error_serializer.rb +13 -7
  48. data/lib/active_model/serializer/errors_serializer.rb +27 -20
  49. data/lib/active_model/serializer/field.rb +2 -0
  50. data/lib/active_model/serializer/fieldset.rb +2 -0
  51. data/lib/active_model/serializer/has_many_reflection.rb +5 -3
  52. data/lib/active_model/serializer/has_one_reflection.rb +3 -4
  53. data/lib/active_model/serializer/lazy_association.rb +99 -0
  54. data/lib/active_model/serializer/link.rb +23 -0
  55. data/lib/active_model/serializer/lint.rb +136 -130
  56. data/lib/active_model/serializer/null.rb +2 -0
  57. data/lib/active_model/serializer/reflection.rb +132 -67
  58. data/lib/active_model/serializer/version.rb +3 -1
  59. data/lib/active_model/serializer.rb +308 -82
  60. data/lib/active_model_serializers/adapter/attributes.rb +5 -66
  61. data/lib/active_model_serializers/adapter/base.rb +41 -39
  62. data/lib/active_model_serializers/adapter/json.rb +2 -0
  63. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +4 -2
  64. data/lib/active_model_serializers/adapter/json_api/error.rb +2 -0
  65. data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +2 -0
  66. data/lib/active_model_serializers/adapter/json_api/link.rb +3 -1
  67. data/lib/active_model_serializers/adapter/json_api/meta.rb +2 -0
  68. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +49 -21
  69. data/lib/active_model_serializers/adapter/json_api/relationship.rb +77 -23
  70. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +41 -10
  71. data/lib/active_model_serializers/adapter/json_api.rb +84 -65
  72. data/lib/active_model_serializers/adapter/null.rb +2 -0
  73. data/lib/active_model_serializers/adapter.rb +9 -1
  74. data/lib/active_model_serializers/callbacks.rb +2 -0
  75. data/lib/active_model_serializers/deprecate.rb +3 -2
  76. data/lib/active_model_serializers/deserialization.rb +4 -0
  77. data/lib/active_model_serializers/json_pointer.rb +2 -0
  78. data/lib/active_model_serializers/logging.rb +2 -0
  79. data/lib/active_model_serializers/lookup_chain.rb +82 -0
  80. data/lib/active_model_serializers/model.rb +111 -28
  81. data/lib/active_model_serializers/railtie.rb +7 -1
  82. data/lib/active_model_serializers/register_jsonapi_renderer.rb +46 -31
  83. data/lib/active_model_serializers/serializable_resource.rb +10 -7
  84. data/lib/active_model_serializers/serialization_context.rb +12 -3
  85. data/lib/active_model_serializers/test/schema.rb +4 -2
  86. data/lib/active_model_serializers/test/serializer.rb +2 -0
  87. data/lib/active_model_serializers/test.rb +2 -0
  88. data/lib/active_model_serializers.rb +35 -10
  89. data/lib/generators/rails/resource_override.rb +3 -1
  90. data/lib/generators/rails/serializer_generator.rb +6 -4
  91. data/lib/grape/active_model_serializers.rb +9 -5
  92. data/lib/grape/formatters/active_model_serializers.rb +21 -2
  93. data/lib/grape/helpers/active_model_serializers.rb +3 -0
  94. data/lib/tasks/rubocop.rake +55 -0
  95. data/test/action_controller/adapter_selector_test.rb +16 -5
  96. data/test/action_controller/explicit_serializer_test.rb +7 -4
  97. data/test/action_controller/json/include_test.rb +108 -27
  98. data/test/action_controller/json_api/deserialization_test.rb +3 -1
  99. data/test/action_controller/json_api/errors_test.rb +10 -9
  100. data/test/action_controller/json_api/fields_test.rb +68 -0
  101. data/test/action_controller/json_api/linked_test.rb +31 -24
  102. data/test/action_controller/json_api/pagination_test.rb +33 -23
  103. data/test/action_controller/json_api/transform_test.rb +13 -3
  104. data/test/action_controller/lookup_proc_test.rb +51 -0
  105. data/test/action_controller/namespace_lookup_test.rb +234 -0
  106. data/test/action_controller/serialization_scope_name_test.rb +14 -6
  107. data/test/action_controller/serialization_test.rb +23 -12
  108. data/test/active_model_serializers/adapter_for_test.rb +2 -0
  109. data/test/active_model_serializers/json_pointer_test.rb +17 -13
  110. data/test/active_model_serializers/logging_test.rb +2 -0
  111. data/test/active_model_serializers/model_test.rb +139 -4
  112. data/test/active_model_serializers/railtie_test_isolated.rb +14 -7
  113. data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +163 -0
  114. data/test/active_model_serializers/serialization_context_test_isolated.rb +25 -10
  115. data/test/active_model_serializers/test/schema_test.rb +5 -2
  116. data/test/active_model_serializers/test/serializer_test.rb +2 -0
  117. data/test/active_record_test.rb +2 -0
  118. data/test/adapter/attributes_test.rb +42 -0
  119. data/test/adapter/deprecation_test.rb +2 -0
  120. data/test/adapter/json/belongs_to_test.rb +2 -0
  121. data/test/adapter/json/collection_test.rb +16 -0
  122. data/test/adapter/json/has_many_test.rb +12 -2
  123. data/test/adapter/json/transform_test.rb +17 -15
  124. data/test/adapter/json_api/belongs_to_test.rb +2 -0
  125. data/test/adapter/json_api/collection_test.rb +6 -3
  126. data/test/adapter/json_api/errors_test.rb +19 -19
  127. data/test/adapter/json_api/fields_test.rb +14 -3
  128. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +2 -0
  129. data/test/adapter/json_api/has_many_test.rb +51 -20
  130. data/test/adapter/json_api/has_one_test.rb +2 -0
  131. data/test/adapter/json_api/include_data_if_sideloaded_test.rb +215 -0
  132. data/test/adapter/json_api/json_api_test.rb +7 -7
  133. data/test/adapter/json_api/linked_test.rb +35 -12
  134. data/test/adapter/json_api/links_test.rb +22 -3
  135. data/test/adapter/json_api/pagination_links_test.rb +55 -13
  136. data/test/adapter/json_api/parse_test.rb +3 -1
  137. data/test/adapter/json_api/relationship_test.rb +311 -73
  138. data/test/adapter/json_api/resource_meta_test.rb +5 -3
  139. data/test/adapter/json_api/toplevel_jsonapi_test.rb +2 -0
  140. data/test/adapter/json_api/transform_test.rb +265 -253
  141. data/test/adapter/json_api/type_test.rb +170 -36
  142. data/test/adapter/json_test.rb +10 -7
  143. data/test/adapter/null_test.rb +3 -2
  144. data/test/adapter/polymorphic_test.rb +54 -5
  145. data/test/adapter_test.rb +3 -1
  146. data/test/array_serializer_test.rb +2 -0
  147. data/test/benchmark/app.rb +3 -1
  148. data/test/benchmark/benchmarking_support.rb +3 -1
  149. data/test/benchmark/bm_active_record.rb +83 -0
  150. data/test/benchmark/bm_adapter.rb +40 -0
  151. data/test/benchmark/bm_caching.rb +18 -16
  152. data/test/benchmark/bm_lookup_chain.rb +85 -0
  153. data/test/benchmark/bm_transform.rb +23 -10
  154. data/test/benchmark/controllers.rb +18 -17
  155. data/test/benchmark/fixtures.rb +74 -72
  156. data/test/cache_test.rb +301 -69
  157. data/test/collection_serializer_test.rb +33 -14
  158. data/test/fixtures/active_record.rb +47 -10
  159. data/test/fixtures/poro.rb +128 -183
  160. data/test/generators/scaffold_controller_generator_test.rb +2 -0
  161. data/test/generators/serializer_generator_test.rb +25 -5
  162. data/test/grape_test.rb +172 -56
  163. data/test/lint_test.rb +3 -1
  164. data/test/logger_test.rb +15 -11
  165. data/test/poro_test.rb +2 -0
  166. data/test/serializable_resource_test.rb +20 -22
  167. data/test/serializers/association_macros_test.rb +5 -2
  168. data/test/serializers/associations_test.rb +274 -49
  169. data/test/serializers/attribute_test.rb +7 -3
  170. data/test/serializers/attributes_test.rb +3 -1
  171. data/test/serializers/caching_configuration_test_isolated.rb +8 -6
  172. data/test/serializers/configuration_test.rb +2 -0
  173. data/test/serializers/fieldset_test.rb +3 -1
  174. data/test/serializers/meta_test.rb +14 -6
  175. data/test/serializers/options_test.rb +19 -6
  176. data/test/serializers/read_attribute_for_serialization_test.rb +5 -3
  177. data/test/serializers/reflection_test.rb +481 -0
  178. data/test/serializers/root_test.rb +3 -1
  179. data/test/serializers/serialization_test.rb +4 -2
  180. data/test/serializers/serializer_for_test.rb +14 -10
  181. data/test/serializers/serializer_for_with_namespace_test.rb +90 -0
  182. data/test/support/isolated_unit.rb +11 -4
  183. data/test/support/rails5_shims.rb +10 -2
  184. data/test/support/rails_app.rb +4 -9
  185. data/test/support/serialization_testing.rb +33 -5
  186. data/test/test_helper.rb +15 -0
  187. metadata +126 -46
  188. data/.rubocop_todo.yml +0 -167
  189. data/docs/ARCHITECTURE.md +0 -126
  190. data/lib/active_model/serializer/associations.rb +0 -100
  191. data/lib/active_model/serializer/attributes.rb +0 -82
  192. data/lib/active_model/serializer/collection_reflection.rb +0 -7
  193. data/lib/active_model/serializer/configuration.rb +0 -35
  194. data/lib/active_model/serializer/include_tree.rb +0 -111
  195. data/lib/active_model/serializer/links.rb +0 -35
  196. data/lib/active_model/serializer/meta.rb +0 -29
  197. data/lib/active_model/serializer/singular_reflection.rb +0 -7
  198. data/lib/active_model/serializer/type.rb +0 -25
  199. data/lib/active_model_serializers/key_transform.rb +0 -70
  200. data/test/active_model_serializers/key_transform_test.rb +0 -263
  201. data/test/adapter/json_api/has_many_embed_ids_test.rb +0 -43
  202. data/test/adapter/json_api/relationships_test.rb +0 -199
  203. data/test/adapter/json_api/resource_identifier_test.rb +0 -85
  204. data/test/include_tree/from_include_args_test.rb +0 -26
  205. data/test/include_tree/from_string_test.rb +0 -94
  206. data/test/include_tree/include_args_to_hash_test.rb +0 -64
@@ -1,50 +1,54 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thread_safe'
4
+ require 'jsonapi/include_directive'
2
5
  require 'active_model/serializer/collection_serializer'
3
6
  require 'active_model/serializer/array_serializer'
4
7
  require 'active_model/serializer/error_serializer'
5
8
  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'
9
+ require 'active_model/serializer/concerns/caching'
11
10
  require 'active_model/serializer/fieldset'
12
11
  require 'active_model/serializer/lint'
13
- require 'active_model/serializer/links'
14
- require 'active_model/serializer/meta'
15
- require 'active_model/serializer/type'
16
12
 
17
13
  # ActiveModel::Serializer is an abstract class that is
18
14
  # reified when subclassed to decorate a resource.
19
15
  module ActiveModel
20
16
  class Serializer
17
+ undef_method :select, :display # These IO methods, which are mixed into Kernel,
18
+ # sometimes conflict with attribute names. We don't need these IO methods.
19
+
21
20
  # @see #serializable_hash for more details on these valid keys.
22
21
  SERIALIZABLE_HASH_VALID_KEYS = [:only, :except, :methods, :include, :root].freeze
23
22
  extend ActiveSupport::Autoload
24
- autoload :Adapter
25
- autoload :Null
26
- include Configuration
27
- include Associations
28
- include Attributes
23
+ eager_autoload do
24
+ autoload :Adapter
25
+ autoload :Null
26
+ autoload :Attribute
27
+ autoload :Link
28
+ autoload :Association
29
+ autoload :Reflection
30
+ autoload :BelongsToReflection
31
+ autoload :HasOneReflection
32
+ autoload :HasManyReflection
33
+ end
34
+ include ActiveSupport::Configurable
29
35
  include Caching
30
- include Links
31
- include Meta
32
- include Type
33
36
 
34
37
  # @param resource [ActiveRecord::Base, ActiveModelSerializers::Model]
35
38
  # @return [ActiveModel::Serializer]
36
39
  # Preferentially returns
37
- # 1. resource.serializer
40
+ # 1. resource.serializer_class
38
41
  # 2. ArraySerializer when resource is a collection
39
42
  # 3. options[:serializer]
40
43
  # 4. lookup serializer when resource is a Class
41
- def self.serializer_for(resource, options = {})
42
- if resource.respond_to?(:serializer_class)
43
- resource.serializer_class
44
- elsif resource.respond_to?(:to_ary)
44
+ def self.serializer_for(resource_or_class, options = {})
45
+ if resource_or_class.respond_to?(:serializer_class)
46
+ resource_or_class.serializer_class
47
+ elsif resource_or_class.respond_to?(:to_ary)
45
48
  config.collection_serializer
46
49
  else
47
- options.fetch(:serializer) { get_serializer_for(resource.class) }
50
+ resource_class = resource_or_class.class == Class ? resource_or_class : resource_or_class.class
51
+ options.fetch(:serializer) { get_serializer_for(resource_class, options[:namespace]) }
48
52
  end
49
53
  end
50
54
 
@@ -59,17 +63,11 @@ module ActiveModel
59
63
  end
60
64
 
61
65
  # @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
66
+ def self.serializer_lookup_chain_for(klass, namespace = nil)
67
+ lookups = ActiveModelSerializers.config.serializer_lookup_chain
68
+ Array[*lookups].flat_map do |lookup|
69
+ lookup.call(klass, self, namespace)
70
+ end.compact
73
71
  end
74
72
 
75
73
  # Used to cache serializer name => serializer class
@@ -84,20 +82,234 @@ module ActiveModel
84
82
  # 1. class name appended with "Serializer"
85
83
  # 2. try again with superclass, if present
86
84
  # 3. nil
87
- def self.get_serializer_for(klass)
85
+ def self.get_serializer_for(klass, namespace = nil)
88
86
  return nil unless config.serializer_lookup_enabled
89
- serializers_cache.fetch_or_store(klass) do
87
+
88
+ cache_key = ActiveSupport::Cache.expand_cache_key(klass, namespace)
89
+ serializers_cache.fetch_or_store(cache_key) do
90
90
  # 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 }
91
+ lookup_chain = serializer_lookup_chain_for(klass, namespace)
92
+ serializer_class = lookup_chain.map(&:safe_constantize).find { |x| x && x < ActiveModel::Serializer }
92
93
 
93
94
  if serializer_class
94
95
  serializer_class
95
96
  elsif klass.superclass
96
97
  get_serializer_for(klass.superclass)
98
+ else
99
+ nil # No serializer found
97
100
  end
98
101
  end
99
102
  end
100
103
 
104
+ # @api private
105
+ def self.include_directive_from_options(options)
106
+ if options[:include_directive]
107
+ options[:include_directive]
108
+ elsif options[:include]
109
+ JSONAPI::IncludeDirective.new(options[:include], allow_wildcard: true)
110
+ else
111
+ ActiveModelSerializers.default_include_directive
112
+ end
113
+ end
114
+
115
+ # @api private
116
+ def self.serialization_adapter_instance
117
+ @serialization_adapter_instance ||= ActiveModelSerializers::Adapter::Attributes
118
+ end
119
+
120
+ # Preferred interface is ActiveModelSerializers.config
121
+ # BEGIN DEFAULT CONFIGURATION
122
+ config.collection_serializer = ActiveModel::Serializer::CollectionSerializer
123
+ config.serializer_lookup_enabled = true
124
+
125
+ # @deprecated Use {#config.collection_serializer=} instead of this. Is
126
+ # compatibility layer for ArraySerializer.
127
+ def config.array_serializer=(collection_serializer)
128
+ self.collection_serializer = collection_serializer
129
+ end
130
+
131
+ # @deprecated Use {#config.collection_serializer} instead of this. Is
132
+ # compatibility layer for ArraySerializer.
133
+ def config.array_serializer
134
+ collection_serializer
135
+ end
136
+
137
+ config.default_includes = '*'
138
+ config.adapter = :attributes
139
+ config.key_transform = nil
140
+ config.jsonapi_pagination_links_enabled = true
141
+ config.jsonapi_resource_type = :plural
142
+ config.jsonapi_namespace_separator = '-'.freeze
143
+ config.jsonapi_version = '1.0'
144
+ config.jsonapi_toplevel_meta = {}
145
+ # Make JSON API top-level jsonapi member opt-in
146
+ # ref: http://jsonapi.org/format/#document-top-level
147
+ config.jsonapi_include_toplevel_object = false
148
+ config.jsonapi_use_foreign_key_on_belongs_to_relationship = false
149
+ config.include_data_default = true
150
+
151
+ # For configuring how serializers are found.
152
+ # This should be an array of procs.
153
+ #
154
+ # The priority of the output is that the first item
155
+ # in the evaluated result array will take precedence
156
+ # over other possible serializer paths.
157
+ #
158
+ # i.e.: First match wins.
159
+ #
160
+ # @example output
161
+ # => [
162
+ # "CustomNamespace::ResourceSerializer",
163
+ # "ParentSerializer::ResourceSerializer",
164
+ # "ResourceNamespace::ResourceSerializer" ,
165
+ # "ResourceSerializer"]
166
+ #
167
+ # If CustomNamespace::ResourceSerializer exists, it will be used
168
+ # for serialization
169
+ config.serializer_lookup_chain = ActiveModelSerializers::LookupChain::DEFAULT.dup
170
+
171
+ config.schema_path = 'test/support/schemas'
172
+ # END DEFAULT CONFIGURATION
173
+
174
+ with_options instance_writer: false, instance_reader: false do |serializer|
175
+ serializer.class_attribute :_attributes_data # @api private
176
+ self._attributes_data ||= {}
177
+ end
178
+ with_options instance_writer: false, instance_reader: true do |serializer|
179
+ serializer.class_attribute :_reflections
180
+ self._reflections ||= {}
181
+ serializer.class_attribute :_links # @api private
182
+ self._links ||= {}
183
+ serializer.class_attribute :_meta # @api private
184
+ serializer.class_attribute :_type # @api private
185
+ end
186
+
187
+ def self.inherited(base)
188
+ super
189
+ base._attributes_data = _attributes_data.dup
190
+ base._reflections = _reflections.dup
191
+ base._links = _links.dup
192
+ end
193
+
194
+ # @return [Array<Symbol>] Key names of declared attributes
195
+ # @see Serializer::attribute
196
+ def self._attributes
197
+ _attributes_data.keys
198
+ end
199
+
200
+ # BEGIN SERIALIZER MACROS
201
+
202
+ # @example
203
+ # class AdminAuthorSerializer < ActiveModel::Serializer
204
+ # attributes :id, :name, :recent_edits
205
+ def self.attributes(*attrs)
206
+ attrs = attrs.first if attrs.first.class == Array
207
+
208
+ attrs.each do |attr|
209
+ attribute(attr)
210
+ end
211
+ end
212
+
213
+ # @example
214
+ # class AdminAuthorSerializer < ActiveModel::Serializer
215
+ # attributes :id, :recent_edits
216
+ # attribute :name, key: :title
217
+ #
218
+ # attribute :full_name do
219
+ # "#{object.first_name} #{object.last_name}"
220
+ # end
221
+ #
222
+ # def recent_edits
223
+ # object.edits.last(5)
224
+ # end
225
+ def self.attribute(attr, options = {}, &block)
226
+ key = options.fetch(:key, attr)
227
+ _attributes_data[key] = Attribute.new(attr, options, block)
228
+ end
229
+
230
+ # @param [Symbol] name of the association
231
+ # @param [Hash<Symbol => any>] options for the reflection
232
+ # @return [void]
233
+ #
234
+ # @example
235
+ # has_many :comments, serializer: CommentSummarySerializer
236
+ #
237
+ def self.has_many(name, options = {}, &block) # rubocop:disable Style/PredicateName
238
+ associate(HasManyReflection.new(name, options, block))
239
+ end
240
+
241
+ # @param [Symbol] name of the association
242
+ # @param [Hash<Symbol => any>] options for the reflection
243
+ # @return [void]
244
+ #
245
+ # @example
246
+ # belongs_to :author, serializer: AuthorSerializer
247
+ #
248
+ def self.belongs_to(name, options = {}, &block)
249
+ associate(BelongsToReflection.new(name, options, block))
250
+ end
251
+
252
+ # @param [Symbol] name of the association
253
+ # @param [Hash<Symbol => any>] options for the reflection
254
+ # @return [void]
255
+ #
256
+ # @example
257
+ # has_one :author, serializer: AuthorSerializer
258
+ #
259
+ def self.has_one(name, options = {}, &block) # rubocop:disable Style/PredicateName
260
+ associate(HasOneReflection.new(name, options, block))
261
+ end
262
+
263
+ # Add reflection and define {name} accessor.
264
+ # @param [ActiveModel::Serializer::Reflection] reflection
265
+ # @return [void]
266
+ #
267
+ # @api private
268
+ def self.associate(reflection)
269
+ key = reflection.options[:key] || reflection.name
270
+ self._reflections[key] = reflection
271
+ end
272
+ private_class_method :associate
273
+
274
+ # Define a link on a serializer.
275
+ # @example
276
+ # link(:self) { resource_url(object) }
277
+ # @example
278
+ # link(:self) { "http://example.com/resource/#{object.id}" }
279
+ # @example
280
+ # link :resource, "http://example.com/resource"
281
+ # @example
282
+ # link(:callback, if: :internal?), { "http://example.com/callback" }
283
+ #
284
+ def self.link(name, *args, &block)
285
+ options = args.extract_options!
286
+ # For compatibility with the use case of passing link directly as string argument
287
+ # without block, we are creating a wrapping block
288
+ _links[name] = Link.new(name, options, block || ->(_serializer) { args.first })
289
+ end
290
+
291
+ # Set the JSON API meta attribute of a serializer.
292
+ # @example
293
+ # class AdminAuthorSerializer < ActiveModel::Serializer
294
+ # meta { stuff: 'value' }
295
+ # @example
296
+ # meta do
297
+ # { comment_count: object.comments.count }
298
+ # end
299
+ def self.meta(value = nil, &block)
300
+ self._meta = block || value
301
+ end
302
+
303
+ # Set the JSON API type of a serializer.
304
+ # @example
305
+ # class AdminAuthorSerializer < ActiveModel::Serializer
306
+ # type 'authors'
307
+ def self.type(type)
308
+ self._type = type && type.to_s
309
+ end
310
+
311
+ # END SERIALIZER MACROS
312
+
101
313
  attr_accessor :object, :root, :scope
102
314
 
103
315
  # `scope_name` is set as :current_user by default in the controller.
@@ -109,63 +321,58 @@ module ActiveModel
109
321
  self.root = instance_options[:root]
110
322
  self.scope = instance_options[:scope]
111
323
 
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
324
+ return if !(scope_name = instance_options[:scope_name]) || respond_to?(scope_name)
325
+
326
+ define_singleton_method scope_name, -> { scope }
116
327
  end
117
328
 
118
329
  def success?
119
330
  true
120
331
  end
121
332
 
333
+ # Return the +attributes+ of +object+ as presented
334
+ # by the serializer.
335
+ def attributes(requested_attrs = nil, reload = false)
336
+ @attributes = nil if reload
337
+ @attributes ||= self.class._attributes_data.each_with_object({}) do |(key, attr), hash|
338
+ next if attr.excluded?(self)
339
+ next unless requested_attrs.nil? || requested_attrs.include?(key)
340
+ hash[key] = attr.value(self)
341
+ end
342
+ end
343
+
344
+ # @param [JSONAPI::IncludeDirective] include_directive (defaults to the
345
+ # +default_include_directive+ config value when not provided)
346
+ # @return [Enumerator<Association>]
347
+ def associations(include_directive = ActiveModelSerializers.default_include_directive, include_slice = nil)
348
+ include_slice ||= include_directive
349
+ return Enumerator.new {} unless object
350
+
351
+ Enumerator.new do |y|
352
+ (self.instance_reflections ||= self.class._reflections.deep_dup).each do |key, reflection|
353
+ next if reflection.excluded?(self)
354
+ next unless include_directive.key?(key)
355
+
356
+ association = reflection.build_association(self, instance_options, include_slice)
357
+ y.yield association
358
+ end
359
+ end
360
+ end
361
+
122
362
  # @return [Hash] containing the attributes and first level
123
363
  # associations, similar to how ActiveModel::Serializers::JSON is used
124
364
  # in ActiveRecord::Base.
125
- #
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>.
129
- # So that the below is true:
130
- # @param options [nil, Hash] The same valid options passed to `serializable_hash`
131
- # (:only, :except, :methods, and :include).
132
- #
133
- # See
134
- # https://github.com/rails/rails/blob/v5.0.0.beta2/activemodel/lib/active_model/serializers/json.rb#L17-L101
135
- # https://github.com/rails/rails/blob/v5.0.0.beta2/activemodel/lib/active_model/serialization.rb#L85-L123
136
- # https://github.com/rails/rails/blob/v5.0.0.beta2/activerecord/lib/active_record/serialization.rb#L11-L17
137
- # https://github.com/rails/rails/blob/v5.0.0.beta2/activesupport/lib/active_support/core_ext/object/json.rb#L147-L162
138
- #
139
- # @example
140
- # # The :only and :except options can be used to limit the attributes included, and work
141
- # # similar to the attributes method.
142
- # serializer.as_json(only: [:id, :name])
143
- # serializer.as_json(except: [:id, :created_at, :age])
144
- #
145
- # # To include the result of some method calls on the model use :methods:
146
- # serializer.as_json(methods: :permalink)
147
- #
148
- # # To include associations use :include:
149
- # serializer.as_json(include: :posts)
150
- # # Second level and higher order associations work as well:
151
- # 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)
365
+ def serializable_hash(adapter_options = nil, options = {}, adapter_instance = self.class.serialization_adapter_instance)
366
+ adapter_options ||= {}
367
+ options[:include_directive] ||= ActiveModel::Serializer.include_directive_from_options(adapter_options)
368
+ resource = attributes_hash(adapter_options, options, adapter_instance)
369
+ relationships = associations_hash(adapter_options, options, adapter_instance)
370
+ resource.merge(relationships)
157
371
  end
158
372
  alias to_hash serializable_hash
159
373
  alias to_h serializable_hash
160
374
 
161
375
  # @see #serializable_hash
162
- # TODO: When moving attributes adapter logic here, @see #serializable_hash
163
- # So that the below is true:
164
- # @param options [nil, Hash] The same valid options passed to `as_json`
165
- # (:root, :only, :except, :methods, and :include).
166
- # The default for `root` is nil.
167
- # The default value for include_root is false. You can change it to true if the given
168
- # JSON string includes a single root node.
169
376
  def as_json(adapter_opts = nil)
170
377
  serializable_hash(adapter_opts)
171
378
  end
@@ -178,15 +385,34 @@ module ActiveModel
178
385
  def read_attribute_for_serialization(attr)
179
386
  if respond_to?(attr)
180
387
  send(attr)
181
- elsif self.class._fragmented
182
- self.class._fragmented.read_attribute_for_serialization(attr)
183
388
  else
184
389
  object.read_attribute_for_serialization(attr)
185
390
  end
186
391
  end
187
392
 
393
+ # @api private
394
+ def attributes_hash(_adapter_options, options, adapter_instance)
395
+ if self.class.cache_enabled?
396
+ fetch_attributes(options[:fields], options[:cached_attributes] || {}, adapter_instance)
397
+ elsif self.class.fragment_cache_enabled?
398
+ fetch_attributes_fragment(adapter_instance, options[:cached_attributes] || {})
399
+ else
400
+ attributes(options[:fields], true)
401
+ end
402
+ end
403
+
404
+ # @api private
405
+ def associations_hash(adapter_options, options, adapter_instance)
406
+ include_directive = options.fetch(:include_directive)
407
+ include_slice = options[:include_slice]
408
+ associations(include_directive, include_slice).each_with_object({}) do |association, relationships|
409
+ adapter_opts = adapter_options.merge(include_directive: include_directive[association.key], adapter_instance: adapter_instance)
410
+ relationships[association.key] = association.serializable_hash(adapter_opts, adapter_instance)
411
+ end
412
+ end
413
+
188
414
  protected
189
415
 
190
- attr_accessor :instance_options
416
+ attr_accessor :instance_options, :instance_reflections
191
417
  end
192
418
  end
@@ -1,75 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModelSerializers
2
4
  module Adapter
3
5
  class Attributes < Base
4
- def initialize(serializer, options = {})
5
- super
6
- @include_tree = ActiveModel::Serializer::IncludeTree.from_include_args(options[:include] || '*')
7
- @cached_attributes = options[:cache_attributes] || {}
8
- end
9
-
10
6
  def serializable_hash(options = nil)
11
7
  options = serialization_options(options)
8
+ options[:fields] ||= instance_options[:fields]
9
+ serialized_hash = serializer.serializable_hash(instance_options, options, self)
12
10
 
13
- if serializer.respond_to?(:each)
14
- serializable_hash_for_collection(options)
15
- else
16
- serializable_hash_for_single_resource(options)
17
- end
18
- end
19
-
20
- private
21
-
22
- def serializable_hash_for_collection(options)
23
- cache_attributes
24
-
25
- serializer.map { |s| Attributes.new(s, instance_options).serializable_hash(options) }
26
- end
27
-
28
- def serializable_hash_for_single_resource(options)
29
- resource = resource_object_for(options)
30
- relationships = resource_relationships(options)
31
- resource.merge(relationships)
32
- end
33
-
34
- def resource_relationships(options)
35
- relationships = {}
36
- serializer.associations(@include_tree).each do |association|
37
- relationships[association.key] ||= relationship_value_for(association, options)
38
- end
39
-
40
- relationships
41
- end
42
-
43
- def relationship_value_for(association, options)
44
- return association.options[:virtual_value] if association.options[:virtual_value]
45
- return unless association.serializer && association.serializer.object
46
-
47
- opts = instance_options.merge(include: @include_tree[association.key])
48
- relationship_value = Attributes.new(association.serializer, opts).serializable_hash(options)
49
-
50
- if association.options[:polymorphic] && relationship_value
51
- polymorphic_type = association.serializer.object.class.name.underscore
52
- relationship_value = { type: polymorphic_type, polymorphic_type.to_sym => relationship_value }
53
- end
54
-
55
- relationship_value
56
- end
57
-
58
- # Set @cached_attributes
59
- def cache_attributes
60
- return if @cached_attributes.present?
61
-
62
- @cached_attributes = ActiveModel::Serializer.cache_read_multi(serializer, self, @include_tree)
63
- end
64
-
65
- def resource_object_for(options)
66
- if serializer.class.cache_enabled?
67
- @cached_attributes.fetch(serializer.cache_key(self)) do
68
- serializer.cached_fields(options[:fields], self)
69
- end
70
- else
71
- serializer.cached_fields(options[:fields], self)
72
- end
11
+ self.class.transform_key_casing!(serialized_hash, instance_options)
73
12
  end
74
13
  end
75
14
  end
@@ -1,4 +1,6 @@
1
- require 'active_model_serializers/key_transform'
1
+ # frozen_string_literal: true
2
+
3
+ require 'case_transform'
2
4
 
3
5
  module ActiveModelSerializers
4
6
  module Adapter
@@ -8,6 +10,40 @@ module ActiveModelSerializers
8
10
  ActiveModelSerializers::Adapter.register(subclass)
9
11
  end
10
12
 
13
+ # Sets the default transform for the adapter.
14
+ #
15
+ # @return [Symbol] the default transform for the adapter
16
+ def self.default_key_transform
17
+ :unaltered
18
+ end
19
+
20
+ # Determines the transform to use in order of precedence:
21
+ # adapter option, global config, adapter default.
22
+ #
23
+ # @param options [Object]
24
+ # @return [Symbol] the transform to use
25
+ def self.transform(options)
26
+ return options[:key_transform] if options && options[:key_transform]
27
+ ActiveModelSerializers.config.key_transform || default_key_transform
28
+ end
29
+
30
+ # Transforms the casing of the supplied value.
31
+ #
32
+ # @param value [Object] the value to be transformed
33
+ # @param options [Object] serializable resource options
34
+ # @return [Symbol] the default transform for the adapter
35
+ def self.transform_key_casing!(value, options)
36
+ CaseTransform.send(transform(options), value)
37
+ end
38
+
39
+ def self.cache_key
40
+ @cache_key ||= ActiveModelSerializers::Adapter.registered_name(self)
41
+ end
42
+
43
+ def self.fragment_cache(cached_hash, non_cached_hash)
44
+ non_cached_hash.merge cached_hash
45
+ end
46
+
11
47
  attr_reader :serializer, :instance_options
12
48
 
13
49
  def initialize(serializer, options = {})
@@ -15,10 +51,6 @@ module ActiveModelSerializers
15
51
  @instance_options = options
16
52
  end
17
53
 
18
- def cached_name
19
- @cached_name ||= self.class.name.demodulize.underscore
20
- end
21
-
22
54
  # Subclasses that implement this method must first call
23
55
  # options = serialization_options(options)
24
56
  def serializable_hash(_options = nil)
@@ -29,14 +61,12 @@ module ActiveModelSerializers
29
61
  serializable_hash(options)
30
62
  end
31
63
 
32
- def fragment_cache(cached_hash, non_cached_hash)
33
- non_cached_hash.merge cached_hash
64
+ def cache_key
65
+ self.class.cache_key
34
66
  end
35
67
 
36
- def cache_check(serializer)
37
- serializer.cache_check(self) do
38
- yield
39
- end
68
+ def fragment_cache(cached_hash, non_cached_hash)
69
+ self.class.fragment_cache(cached_hash, non_cached_hash)
40
70
  end
41
71
 
42
72
  private
@@ -50,34 +80,6 @@ module ActiveModelSerializers
50
80
  def root
51
81
  serializer.json_key.to_sym if serializer.json_key
52
82
  end
53
-
54
- class << self
55
- # Sets the default transform for the adapter.
56
- #
57
- # @return [Symbol] the default transform for the adapter
58
- def default_key_transform
59
- :unaltered
60
- end
61
-
62
- # Determines the transform to use in order of precedence:
63
- # adapter option, global config, adapter default.
64
- #
65
- # @param options [Object]
66
- # @return [Symbol] the transform to use
67
- def transform(options)
68
- return options[:key_transform] if options && options[:key_transform]
69
- ActiveModelSerializers.config.key_transform || default_key_transform
70
- end
71
-
72
- # Transforms the casing of the supplied value.
73
- #
74
- # @param value [Object] the value to be transformed
75
- # @param options [Object] serializable resource options
76
- # @return [Symbol] the default transform for the adapter
77
- def transform_key_casing!(value, options)
78
- KeyTransform.send(transform(options), value)
79
- end
80
- end
81
83
  end
82
84
  end
83
85
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModelSerializers
2
4
  module Adapter
3
5
  class Json < Base