active_model_serializers 0.10.0 → 0.10.7

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