active_model_serializers 0.10.0 → 0.10.13

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