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