active_model_serializers 0.9.8 → 0.10.15

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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +646 -59
  3. data/MIT-LICENSE +3 -2
  4. data/README.md +194 -846
  5. data/lib/action_controller/serialization.rb +35 -66
  6. data/lib/active_model/serializable_resource.rb +13 -0
  7. data/lib/active_model/serializer/adapter/attributes.rb +17 -0
  8. data/lib/active_model/serializer/adapter/base.rb +20 -0
  9. data/lib/active_model/serializer/adapter/json.rb +17 -0
  10. data/lib/active_model/serializer/adapter/json_api.rb +17 -0
  11. data/lib/active_model/serializer/adapter/null.rb +17 -0
  12. data/lib/active_model/serializer/adapter.rb +26 -0
  13. data/lib/active_model/serializer/array_serializer.rb +14 -0
  14. data/lib/active_model/serializer/association.rb +55 -40
  15. data/lib/active_model/serializer/attribute.rb +27 -0
  16. data/lib/active_model/serializer/belongs_to_reflection.rb +13 -0
  17. data/lib/active_model/serializer/collection_serializer.rb +99 -0
  18. data/lib/active_model/serializer/concerns/caching.rb +305 -0
  19. data/lib/active_model/serializer/error_serializer.rb +16 -0
  20. data/lib/active_model/serializer/errors_serializer.rb +34 -0
  21. data/lib/active_model/serializer/field.rb +92 -0
  22. data/lib/active_model/serializer/fieldset.rb +33 -0
  23. data/lib/active_model/serializer/has_many_reflection.rb +12 -0
  24. data/lib/active_model/serializer/has_one_reflection.rb +9 -0
  25. data/lib/active_model/serializer/lazy_association.rb +99 -0
  26. data/lib/active_model/serializer/link.rb +23 -0
  27. data/lib/active_model/serializer/lint.rb +152 -0
  28. data/lib/active_model/serializer/null.rb +19 -0
  29. data/lib/active_model/serializer/reflection.rb +212 -0
  30. data/lib/active_model/serializer/version.rb +3 -1
  31. data/lib/active_model/serializer.rb +363 -254
  32. data/lib/active_model_serializers/adapter/attributes.rb +36 -0
  33. data/lib/active_model_serializers/adapter/base.rb +85 -0
  34. data/lib/active_model_serializers/adapter/json.rb +23 -0
  35. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +215 -0
  36. data/lib/active_model_serializers/adapter/json_api/error.rb +98 -0
  37. data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +51 -0
  38. data/lib/active_model_serializers/adapter/json_api/link.rb +85 -0
  39. data/lib/active_model_serializers/adapter/json_api/meta.rb +39 -0
  40. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +94 -0
  41. data/lib/active_model_serializers/adapter/json_api/relationship.rb +106 -0
  42. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +68 -0
  43. data/lib/active_model_serializers/adapter/json_api.rb +535 -0
  44. data/lib/active_model_serializers/adapter/null.rb +11 -0
  45. data/lib/active_model_serializers/adapter.rb +100 -0
  46. data/lib/active_model_serializers/callbacks.rb +57 -0
  47. data/lib/active_model_serializers/deprecate.rb +56 -0
  48. data/lib/active_model_serializers/deserialization.rb +17 -0
  49. data/lib/active_model_serializers/json_pointer.rb +16 -0
  50. data/lib/active_model_serializers/logging.rb +124 -0
  51. data/lib/active_model_serializers/lookup_chain.rb +82 -0
  52. data/lib/active_model_serializers/model.rb +132 -0
  53. data/lib/active_model_serializers/railtie.rb +62 -0
  54. data/lib/active_model_serializers/register_jsonapi_renderer.rb +80 -0
  55. data/lib/active_model_serializers/serializable_resource.rb +84 -0
  56. data/lib/active_model_serializers/serialization_context.rb +41 -0
  57. data/lib/active_model_serializers/test/schema.rb +140 -0
  58. data/lib/active_model_serializers/test/serializer.rb +127 -0
  59. data/lib/active_model_serializers/test.rb +9 -0
  60. data/lib/active_model_serializers.rb +60 -17
  61. data/lib/generators/rails/USAGE +6 -0
  62. data/lib/{active_model/serializer/generators → generators/rails}/resource_override.rb +3 -4
  63. data/lib/{active_model/serializer/generators/serializer → generators/rails}/serializer_generator.rb +6 -5
  64. data/lib/grape/active_model_serializers.rb +18 -0
  65. data/lib/grape/formatters/active_model_serializers.rb +34 -0
  66. data/lib/grape/helpers/active_model_serializers.rb +19 -0
  67. data/lib/tasks/rubocop.rake +60 -0
  68. metadata +249 -157
  69. data/CONTRIBUTING.md +0 -20
  70. data/DESIGN.textile +0 -586
  71. data/lib/action_controller/serialization_test_case.rb +0 -79
  72. data/lib/active_model/array_serializer.rb +0 -68
  73. data/lib/active_model/default_serializer.rb +0 -28
  74. data/lib/active_model/serializable/utils.rb +0 -16
  75. data/lib/active_model/serializable.rb +0 -59
  76. data/lib/active_model/serializer/association/has_many.rb +0 -39
  77. data/lib/active_model/serializer/association/has_one.rb +0 -25
  78. data/lib/active_model/serializer/config.rb +0 -31
  79. data/lib/active_model/serializer/generators/serializer/USAGE +0 -9
  80. data/lib/active_model/serializer/generators/serializer/scaffold_controller_generator.rb +0 -14
  81. data/lib/active_model/serializer/generators/serializer/templates/controller.rb +0 -93
  82. data/lib/active_model/serializer/railtie.rb +0 -22
  83. data/lib/active_model/serializer_support.rb +0 -5
  84. data/lib/active_model_serializers/model/caching.rb +0 -25
  85. data/test/benchmark/app.rb +0 -60
  86. data/test/benchmark/benchmarking_support.rb +0 -67
  87. data/test/benchmark/bm_active_record.rb +0 -41
  88. data/test/benchmark/setup.rb +0 -75
  89. data/test/benchmark/tmp/miniprofiler/mp_timers_6eqewtfgrhitvq5gqm25 +0 -0
  90. data/test/benchmark/tmp/miniprofiler/mp_timers_8083sx03hu72pxz1a4d0 +0 -0
  91. data/test/benchmark/tmp/miniprofiler/mp_timers_fyz2gsml4z0ph9kpoy1c +0 -0
  92. data/test/benchmark/tmp/miniprofiler/mp_timers_hjry5rc32imd42oxoi48 +0 -0
  93. data/test/benchmark/tmp/miniprofiler/mp_timers_m8fpoz2cvt3g9agz0bs3 +0 -0
  94. data/test/benchmark/tmp/miniprofiler/mp_timers_p92m2drnj1i568u3sta0 +0 -0
  95. data/test/benchmark/tmp/miniprofiler/mp_timers_qg52tpca3uesdfguee9i +0 -0
  96. data/test/benchmark/tmp/miniprofiler/mp_timers_s15t1a6mvxe0z7vjv790 +0 -0
  97. data/test/benchmark/tmp/miniprofiler/mp_timers_x8kal3d17nfds6vp4kcj +0 -0
  98. data/test/benchmark/tmp/miniprofiler/mp_views_127.0.0.1 +0 -0
  99. data/test/fixtures/active_record.rb +0 -96
  100. data/test/fixtures/poro.rb +0 -223
  101. data/test/fixtures/template.html.erb +0 -1
  102. data/test/integration/action_controller/namespaced_serialization_test.rb +0 -105
  103. data/test/integration/action_controller/serialization_test.rb +0 -287
  104. data/test/integration/action_controller/serialization_test_case_test.rb +0 -71
  105. data/test/integration/active_record/active_record_test.rb +0 -94
  106. data/test/integration/generators/resource_generator_test.rb +0 -26
  107. data/test/integration/generators/scaffold_controller_generator_test.rb +0 -64
  108. data/test/integration/generators/serializer_generator_test.rb +0 -41
  109. data/test/test_app.rb +0 -14
  110. data/test/test_helper.rb +0 -24
  111. data/test/tmp/app/assets/javascripts/accounts.js +0 -2
  112. data/test/tmp/app/assets/stylesheets/accounts.css +0 -4
  113. data/test/tmp/app/controllers/accounts_controller.rb +0 -2
  114. data/test/tmp/app/helpers/accounts_helper.rb +0 -2
  115. data/test/tmp/app/serializers/account_serializer.rb +0 -3
  116. data/test/tmp/config/routes.rb +0 -1
  117. data/test/unit/active_model/array_serializer/except_test.rb +0 -18
  118. data/test/unit/active_model/array_serializer/key_format_test.rb +0 -18
  119. data/test/unit/active_model/array_serializer/meta_test.rb +0 -53
  120. data/test/unit/active_model/array_serializer/only_test.rb +0 -18
  121. data/test/unit/active_model/array_serializer/options_test.rb +0 -16
  122. data/test/unit/active_model/array_serializer/root_test.rb +0 -102
  123. data/test/unit/active_model/array_serializer/scope_test.rb +0 -24
  124. data/test/unit/active_model/array_serializer/serialization_test.rb +0 -216
  125. data/test/unit/active_model/default_serializer_test.rb +0 -13
  126. data/test/unit/active_model/serializer/associations/build_serializer_test.rb +0 -36
  127. data/test/unit/active_model/serializer/associations_test.rb +0 -49
  128. data/test/unit/active_model/serializer/attributes_test.rb +0 -57
  129. data/test/unit/active_model/serializer/config_test.rb +0 -91
  130. data/test/unit/active_model/serializer/filter_test.rb +0 -69
  131. data/test/unit/active_model/serializer/has_many_polymorphic_test.rb +0 -189
  132. data/test/unit/active_model/serializer/has_many_test.rb +0 -265
  133. data/test/unit/active_model/serializer/has_one_and_has_many_test.rb +0 -27
  134. data/test/unit/active_model/serializer/has_one_polymorphic_test.rb +0 -196
  135. data/test/unit/active_model/serializer/has_one_test.rb +0 -253
  136. data/test/unit/active_model/serializer/key_format_test.rb +0 -25
  137. data/test/unit/active_model/serializer/meta_test.rb +0 -39
  138. data/test/unit/active_model/serializer/options_test.rb +0 -42
  139. data/test/unit/active_model/serializer/root_test.rb +0 -117
  140. data/test/unit/active_model/serializer/scope_test.rb +0 -49
  141. data/test/unit/active_model/serializer/url_helpers_test.rb +0 -35
  142. data/test/unit/active_model/serilizable_test.rb +0 -50
  143. /data/lib/{active_model/serializer/generators/serializer/templates/serializer.rb → generators/rails/templates/serializer.rb.erb} +0 -0
@@ -1,318 +1,427 @@
1
- require 'active_model/array_serializer'
2
- require 'active_model/serializable'
3
- require 'active_model/serializer/association'
4
- require 'active_model/serializer/config'
5
-
6
- require 'thread'
7
- require 'concurrent/map'
8
-
1
+ # frozen_string_literal: true
2
+
3
+ require 'jsonapi/include_directive'
4
+ require 'active_model/serializer/collection_serializer'
5
+ require 'active_model/serializer/array_serializer'
6
+ require 'active_model/serializer/error_serializer'
7
+ require 'active_model/serializer/errors_serializer'
8
+ require 'active_model/serializer/concerns/caching'
9
+ require 'active_model/serializer/fieldset'
10
+ require 'active_model/serializer/lint'
11
+
12
+ # ActiveModel::Serializer is an abstract class that is
13
+ # reified when subclassed to decorate a resource.
9
14
  module ActiveModel
10
15
  class Serializer
11
- include Serializable
12
-
13
- @mutex = Mutex.new
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
+
19
+ # @see #serializable_hash for more details on these valid keys.
20
+ SERIALIZABLE_HASH_VALID_KEYS = [:only, :except, :methods, :include, :root].freeze
21
+ extend ActiveSupport::Autoload
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
34
+ include Caching
35
+
36
+ # @param resource [ActiveRecord::Base, ActiveModelSerializers::Model]
37
+ # @return [ActiveModel::Serializer]
38
+ # Preferentially returns
39
+ # 1. resource.serializer_class
40
+ # 2. ArraySerializer when resource is a collection
41
+ # 3. options[:serializer]
42
+ # 4. lookup serializer when resource is a Class
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)
47
+ config.collection_serializer
48
+ else
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]) }
51
+ end
52
+ end
14
53
 
54
+ # @see ActiveModelSerializers::Adapter.lookup
55
+ # Deprecated
56
+ def self.adapter
57
+ ActiveModelSerializers::Adapter.lookup(config.adapter)
58
+ end
15
59
  class << self
16
- def inherited(base)
17
- base._root = _root
18
- base._attributes = (_attributes || []).dup
19
- base._associations = (_associations || {}).dup
20
- end
60
+ extend ActiveModelSerializers::Deprecate
61
+ deprecate :adapter, 'ActiveModelSerializers::Adapter.configured_adapter'
62
+ end
21
63
 
22
- def setup
23
- @mutex.synchronize do
24
- yield CONFIG
25
- end
26
- end
64
+ # @api private
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
70
+ end
27
71
 
28
- EMBED_IN_ROOT_OPTIONS = [
29
- :include,
30
- :embed_in_root,
31
- :embed_in_root_key,
32
- :embed_namespace
33
- ].freeze
34
-
35
- def embed(type, options={})
36
- CONFIG.embed = type
37
- if EMBED_IN_ROOT_OPTIONS.any? { |opt| options[opt].present? }
38
- CONFIG.embed_in_root = true
39
- end
40
- if options[:embed_in_root_key].present?
41
- CONFIG.embed_in_root_key = options[:embed_in_root_key]
42
- end
43
- ActiveSupport::Deprecation.warn <<-WARN
44
- ** Notice: embed is deprecated. **
45
- The use of .embed method on a Serializer will be soon removed, as this should have a global scope and not a class scope.
46
- Please use the global .setup method instead:
47
- ActiveModel::Serializer.setup do |config|
48
- config.embed = :#{type}
49
- config.embed_in_root = #{CONFIG.embed_in_root || false}
50
- end
51
- WARN
52
- end
72
+ # Used to cache serializer name => serializer class
73
+ # when looked up by Serializer.get_serializer_for.
74
+ def self.serializers_cache
75
+ @serializers_cache ||= Concurrent::Map.new
76
+ end
53
77
 
54
- def format_keys(format)
55
- @key_format = format
56
- end
57
- attr_reader :key_format
58
-
59
- def serializer_for(resource, options = {})
60
- if resource.respond_to?(:serializer_class)
61
- resource.serializer_class
62
- elsif resource.respond_to?(:to_ary)
63
- if Object.constants.include?(:ArraySerializer)
64
- ::ArraySerializer
65
- else
66
- ArraySerializer
67
- end
78
+ # @api private
79
+ # Find a serializer from a class and caches the lookup.
80
+ # Preferentially returns:
81
+ # 1. class name appended with "Serializer"
82
+ # 2. try again with superclass, if present
83
+ # 3. nil
84
+ def self.get_serializer_for(klass, namespace = nil)
85
+ return nil unless config.serializer_lookup_enabled
86
+
87
+ cache_key = ActiveSupport::Cache.expand_cache_key(klass, namespace)
88
+ serializers_cache.fetch_or_store(cache_key) do
89
+ # NOTE(beauby): When we drop 1.9.3 support we can lazify the map for perfs.
90
+ lookup_chain = serializer_lookup_chain_for(klass, namespace)
91
+ serializer_class = lookup_chain.map(&:safe_constantize).find { |x| x && x < ActiveModel::Serializer }
92
+
93
+ if serializer_class
94
+ serializer_class
95
+ elsif klass.superclass
96
+ get_serializer_for(klass.superclass, namespace)
68
97
  else
69
- klass_name = build_serializer_class(resource, options)
70
- Serializer.serializers_cache.fetch_or_store(klass_name) do
71
- _const_get(klass_name)
72
- end
98
+ nil # No serializer found
73
99
  end
74
100
  end
101
+ end
75
102
 
76
- attr_accessor :_root, :_attributes, :_associations
77
- alias root _root=
78
- alias root= _root=
79
-
80
- def root_name
81
- if name
82
- root_name = name.demodulize.underscore.sub(/_serializer$/, '')
83
- CONFIG.plural_default_root ? root_name.pluralize : root_name
84
- end
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
85
111
  end
112
+ end
86
113
 
87
- def attributes(*attrs)
88
- attrs.each do |attr|
89
- striped_attr = strip_attribute attr
114
+ # @api private
115
+ def self.serialization_adapter_instance
116
+ @serialization_adapter_instance ||= ActiveModelSerializers::Adapter::Attributes
117
+ end
90
118
 
91
- @_attributes << striped_attr
119
+ # Preferred interface is ActiveModelSerializers.config
120
+ # BEGIN DEFAULT CONFIGURATION
121
+ config.collection_serializer = ActiveModel::Serializer::CollectionSerializer
122
+ config.serializer_lookup_enabled = true
92
123
 
93
- define_method striped_attr do
94
- object.read_attribute_for_serialization attr
95
- end unless method_defined?(attr)
96
- end
97
- end
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
98
129
 
99
- def has_one(*attrs)
100
- associate(Association::HasOne, *attrs)
101
- end
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
102
135
 
103
- def has_many(*attrs)
104
- associate(Association::HasMany, *attrs)
105
- end
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
106
187
 
107
- def serializers_cache
108
- @serializers_cache ||= Concurrent::Map.new
109
- end
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
110
194
 
111
- private
195
+ # @return [Array<Symbol>] Key names of declared attributes
196
+ # @see Serializer::attribute
197
+ def self._attributes
198
+ _attributes_data.keys
199
+ end
112
200
 
113
- def strip_attribute(attr)
114
- symbolized = attr.is_a?(Symbol)
201
+ # BEGIN SERIALIZER MACROS
115
202
 
116
- attr = attr.to_s.gsub(/\?\Z/, '')
117
- attr = attr.to_sym if symbolized
118
- attr
119
- end
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
120
208
 
121
- def build_serializer_class(resource, options)
122
- "".tap do |klass_name|
123
- klass_name << "#{options[:namespace]}::" if options[:namespace]
124
- klass_name << options[:prefix].to_s.classify if options[:prefix]
125
- klass_name << "#{resource.class.name}Serializer"
126
- end
209
+ attrs.each do |attr|
210
+ attribute(attr)
127
211
  end
212
+ end
128
213
 
129
- def associate(klass, *attrs)
130
- options = attrs.extract_options!
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
131
230
 
132
- attrs.each do |attr|
133
- define_method attr do
134
- object.send attr
135
- end unless method_defined?(attr)
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
136
241
 
137
- @_associations[attr] = klass.new(attr, options)
138
- end
139
- end
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))
140
251
  end
141
252
 
142
- def initialize(object, options={})
143
- @object = object
144
- @scope = options[:scope]
145
- @root = options.fetch(:root, self.class._root)
146
- @polymorphic = options.fetch(:polymorphic, false)
147
- @meta_key = options[:meta_key] || :meta
148
- @meta = options[@meta_key]
149
- @wrap_in_array = options[:_wrap_in_array]
150
- @only = options[:only] ? Array(options[:only]) : nil
151
- @except = options[:except] ? Array(options[:except]) : nil
152
- @key_format = options[:key_format]
153
- @context = options[:context]
154
- @namespace = options[:namespace]
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))
155
262
  end
156
- attr_accessor :object, :scope, :root, :meta_key, :meta, :key_format, :context, :polymorphic
157
263
 
158
- def json_key
159
- key = if root == true || root.nil?
160
- self.class.root_name
161
- else
162
- root
163
- end
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
164
291
 
165
- key_format == :lower_camel && key.present? ? key.camelize(:lower) : key
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
166
302
  end
167
303
 
168
- def attributes
169
- filter(self.class._attributes.dup).each_with_object({}) do |name, hash|
170
- hash[name] = send(name)
171
- end
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
172
310
  end
173
311
 
174
- def associations(options={})
175
- associations = self.class._associations
176
- included_associations = filter(associations.keys)
177
- associations.each_with_object({}) do |(name, association), hash|
178
- if included_associations.include? name
179
- if association.embed_ids?
180
- ids = serialize_ids association
181
- if association.embed_namespace?
182
- hash = hash[association.embed_namespace] ||= {}
183
- hash[association.key] = ids
184
- else
185
- hash[association.key] = ids
186
- end
187
- elsif association.embed_objects?
188
- if association.embed_namespace?
189
- hash = hash[association.embed_namespace] ||= {}
190
- end
191
- hash[association.embedded_key] = serialize association, options
192
- end
193
- end
194
- end
312
+ # END SERIALIZER MACROS
313
+
314
+ attr_accessor :object, :root, :scope
315
+
316
+ # `scope_name` is set as :current_user by default in the controller.
317
+ # If the instance does not have a method named `scope_name`, it
318
+ # defines the method so that it calls the +scope+.
319
+ def initialize(object, options = {})
320
+ self.object = object
321
+ self.instance_options = options
322
+ self.root = instance_options[:root]
323
+ self.scope = instance_options[:scope]
324
+
325
+ return if !(scope_name = instance_options[:scope_name]) || respond_to?(scope_name)
326
+
327
+ define_singleton_method scope_name, -> { scope }
195
328
  end
196
329
 
197
- def filter(keys)
198
- if @only
199
- keys & @only
200
- elsif @except
201
- keys - @except
202
- else
203
- keys
204
- end
330
+ def success?
331
+ true
205
332
  end
206
333
 
207
- def embedded_in_root_associations
208
- associations = self.class._associations
209
- included_associations = filter(associations.keys)
210
- associations.each_with_object({}) do |(name, association), hash|
211
- if included_associations.include? name
212
- association_serializer = build_serializer(association)
213
- # we must do this always because even if the current association is not
214
- # embedded in root, it might have its own associations that are embedded in root
215
- hash.merge!(association_serializer.embedded_in_root_associations) do |key, oldval, newval|
216
- if oldval.respond_to?(:to_ary)
217
- [oldval, newval].flatten.uniq
218
- else
219
- oldval.merge(newval) { |_, oldval, newval| [oldval, newval].flatten.uniq }
220
- end
221
- end
222
-
223
- if association.embed_in_root?
224
- if association.embed_in_root_key?
225
- hash = hash[association.embed_in_root_key] ||= {}
226
- end
227
-
228
- serialized_data = association_serializer.serializable_object
229
- key = association.root_key
230
- if hash.has_key?(key)
231
- hash[key].concat(serialized_data).uniq!
232
- else
233
- hash[key] = serialized_data
234
- end
235
- end
236
- end
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)
237
342
  end
238
343
  end
239
344
 
240
- def build_serializer(association)
241
- object = send(association.name)
242
- association.build_serializer(object, association_options_for_serializer(association))
243
- end
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
244
351
 
245
- def association_options_for_serializer(association)
246
- prefix = association.options[:prefix]
247
- namespace = association.options[:namespace] || @namespace || self.namespace
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)
248
356
 
249
- { scope: scope }.tap do |opts|
250
- opts[:namespace] = namespace if namespace
251
- opts[:prefix] = prefix if prefix
357
+ association = reflection.build_association(self, instance_options, include_slice)
358
+ y.yield association
359
+ end
252
360
  end
253
361
  end
254
362
 
255
- def serialize(association,options={})
256
- build_serializer(association).serializable_object(options)
363
+ # @return [Hash] containing the attributes and first level
364
+ # associations, similar to how ActiveModel::Serializers::JSON is used
365
+ # in ActiveRecord::Base.
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)
257
375
  end
376
+ alias to_hash serializable_hash
377
+ alias to_h serializable_hash
258
378
 
259
- def serialize_ids(association)
260
- associated_data = send(association.name)
261
- if associated_data.respond_to?(:to_ary)
262
- associated_data.map { |elem| serialize_id(elem, association) }
263
- else
264
- serialize_id(associated_data, association) if associated_data
265
- end
379
+ # @see #serializable_hash
380
+ def as_json(adapter_opts = nil)
381
+ serializable_hash(adapter_opts)
266
382
  end
267
383
 
268
- def key_format
269
- @key_format || self.class.key_format || CONFIG.key_format
384
+ # Used by adapter as resource root.
385
+ def json_key
386
+ root || _type ||
387
+ begin
388
+ object.class.model_name.to_s.underscore
389
+ rescue ArgumentError
390
+ 'anonymous_object'
391
+ end
270
392
  end
271
393
 
272
- def format_key(key)
273
- if key_format == :lower_camel
274
- key.to_s.camelize(:lower)
394
+ def read_attribute_for_serialization(attr)
395
+ if respond_to?(attr)
396
+ send(attr)
275
397
  else
276
- key
398
+ object.read_attribute_for_serialization(attr)
277
399
  end
278
400
  end
279
401
 
280
- def convert_keys(hash)
281
- Hash[hash.map do |k,v|
282
- key = if k.is_a?(Symbol)
283
- format_key(k).to_sym
284
- else
285
- format_key(k)
286
- end
287
-
288
- [key ,v]
289
- end]
290
- end
291
-
292
- attr_writer :serialization_options
293
- def serialization_options
294
- @serialization_options || {}
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
295
411
  end
296
412
 
297
- def serializable_object(options={})
298
- self.serialization_options = options
299
- return @wrap_in_array ? [] : nil if @object.nil?
300
- hash = attributes
301
- hash.merge! associations(options)
302
- hash = convert_keys(hash) if key_format.present?
303
- hash = { :type => type_name(@object), type_name(@object) => hash } if @polymorphic
304
- @wrap_in_array ? [hash] : hash
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
305
421
  end
306
- alias_method :serializable_hash, :serializable_object
307
422
 
308
- def serialize_id(elem, association)
309
- id = elem.read_attribute_for_serialization(association.embed_key)
310
- association.polymorphic? ? { id: id, type: type_name(elem) } : id
311
- end
423
+ protected
312
424
 
313
- def type_name(elem)
314
- elem.class.to_s.demodulize.underscore.to_sym
315
- end
425
+ attr_accessor :instance_options, :instance_reflections
316
426
  end
317
-
318
427
  end