active_model_serializers 0.9.9 → 0.10.14

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 +634 -61
  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 -269
  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 +52 -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 +248 -155
  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/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 -254
  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 -14
  109. data/test/test_helper.rb +0 -26
  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 -35
  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,333 +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
- search_list = build_serializer_class_list(resource, options)
70
- result = search_list.map do |klass_name|
71
- Serializer.serializers_cache.fetch_or_store(klass_name) do
72
- _const_get(klass_name)
73
- end
74
- end
75
-
76
- result.find { |serializer| !serializer.nil? }
98
+ nil # No serializer found
77
99
  end
78
100
  end
101
+ end
79
102
 
80
- attr_accessor :_root, :_attributes, :_associations
81
- alias root _root=
82
- alias root= _root=
83
-
84
- def root_name
85
- if name
86
- root_name = name.demodulize.underscore.sub(/_serializer$/, '')
87
- CONFIG.plural_default_root ? root_name.pluralize : root_name
88
- 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
89
111
  end
112
+ end
90
113
 
91
- def attributes(*attrs)
92
- attrs.each do |attr|
93
- striped_attr = strip_attribute attr
94
-
95
- @_attributes << striped_attr
114
+ # @api private
115
+ def self.serialization_adapter_instance
116
+ @serialization_adapter_instance ||= ActiveModelSerializers::Adapter::Attributes
117
+ end
96
118
 
97
- define_method striped_attr do
98
- object.read_attribute_for_serialization attr
99
- end unless method_defined?(attr)
100
- end
101
- end
119
+ # Preferred interface is ActiveModelSerializers.config
120
+ # BEGIN DEFAULT CONFIGURATION
121
+ config.collection_serializer = ActiveModel::Serializer::CollectionSerializer
122
+ config.serializer_lookup_enabled = true
102
123
 
103
- def has_one(*attrs)
104
- associate(Association::HasOne, *attrs)
105
- 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
106
129
 
107
- def has_many(*attrs)
108
- associate(Association::HasMany, *attrs)
109
- 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
110
135
 
111
- def serializers_cache
112
- @serializers_cache ||= Concurrent::Map.new
113
- 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
114
187
 
115
- 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
116
194
 
117
- def strip_attribute(attr)
118
- 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
119
200
 
120
- attr = attr.to_s.gsub(/\?\Z/, '')
121
- attr = attr.to_sym if symbolized
122
- attr
123
- end
201
+ # BEGIN SERIALIZER MACROS
124
202
 
125
- def build_serializer_class_list(resource, options)
126
- list = []
127
- list << build_serializer_class(resource, options)
128
- list << build_serializer_class(resource, {})
129
- list << 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
- "".tap do |klass_name|
134
- klass_name << "#{options[:namespace]}::" if options[:namespace]
135
- klass_name << options[:prefix].to_s.classify if options[:prefix]
136
- if resource.is_a?(String)
137
- klass_name << "#{resource}Serializer"
138
- else
139
- klass_name << "#{resource.class.name}Serializer"
140
- end
141
- end
209
+ attrs.each do |attr|
210
+ attribute(attr)
142
211
  end
212
+ end
143
213
 
144
- def associate(klass, *attrs)
145
- 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
146
230
 
147
- attrs.each do |attr|
148
- define_method attr do
149
- object.send attr
150
- 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
151
241
 
152
- @_associations[attr] = klass.new(attr, options)
153
- end
154
- 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))
155
251
  end
156
252
 
157
- def initialize(object, options={})
158
- @object = object
159
- @scope = options[:scope]
160
- @root = options.fetch(:root, self.class._root)
161
- @polymorphic = options.fetch(:polymorphic, false)
162
- @meta_key = options[:meta_key] || :meta
163
- @meta = options[@meta_key]
164
- @wrap_in_array = options[:_wrap_in_array]
165
- @only = options[:only] ? Array(options[:only]) : nil
166
- @except = options[:except] ? Array(options[:except]) : nil
167
- @key_format = options[:key_format]
168
- @context = options[:context]
169
- @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))
170
262
  end
171
- attr_accessor :object, :scope, :root, :meta_key, :meta, :key_format, :context, :polymorphic
172
263
 
173
- def json_key
174
- key = if root == true || root.nil?
175
- self.class.root_name
176
- else
177
- root
178
- 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
179
291
 
180
- 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
181
302
  end
182
303
 
183
- def attributes
184
- filter(self.class._attributes.dup).each_with_object({}) do |name, hash|
185
- hash[name] = send(name)
186
- 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
187
310
  end
188
311
 
189
- def associations(options={})
190
- associations = self.class._associations
191
- included_associations = filter(associations.keys)
192
- associations.each_with_object({}) do |(name, association), hash|
193
- if included_associations.include? name
194
- if association.embed_ids?
195
- ids = serialize_ids association
196
- if association.embed_namespace?
197
- hash = hash[association.embed_namespace] ||= {}
198
- hash[association.key] = ids
199
- else
200
- hash[association.key] = ids
201
- end
202
- elsif association.embed_objects?
203
- if association.embed_namespace?
204
- hash = hash[association.embed_namespace] ||= {}
205
- end
206
- hash[association.embedded_key] = serialize association, options
207
- end
208
- end
209
- 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 }
210
328
  end
211
329
 
212
- def filter(keys)
213
- if @only
214
- keys & @only
215
- elsif @except
216
- keys - @except
217
- else
218
- keys
219
- end
330
+ def success?
331
+ true
220
332
  end
221
333
 
222
- def embedded_in_root_associations
223
- associations = self.class._associations
224
- included_associations = filter(associations.keys)
225
- associations.each_with_object({}) do |(name, association), hash|
226
- if included_associations.include? name
227
- association_serializer = build_serializer(association)
228
- # we must do this always because even if the current association is not
229
- # embedded in root, it might have its own associations that are embedded in root
230
- hash.merge!(association_serializer.embedded_in_root_associations) do |key, oldval, newval|
231
- if oldval.respond_to?(:to_ary)
232
- [oldval, newval].flatten.uniq
233
- else
234
- oldval.merge(newval) { |_, oldval, newval| [oldval, newval].flatten.uniq }
235
- end
236
- end
237
-
238
- if association.embed_in_root?
239
- if association.embed_in_root_key?
240
- hash = hash[association.embed_in_root_key] ||= {}
241
- end
242
-
243
- serialized_data = association_serializer.serializable_object
244
- key = association.root_key
245
- if hash.has_key?(key)
246
- hash[key].concat(serialized_data).uniq!
247
- else
248
- hash[key] = serialized_data
249
- end
250
- end
251
- 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)
252
342
  end
253
343
  end
254
344
 
255
- def build_serializer(association)
256
- object = send(association.name)
257
- association.build_serializer(object, association_options_for_serializer(association))
258
- 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
259
351
 
260
- def association_options_for_serializer(association)
261
- prefix = association.options[:prefix]
262
- 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)
263
356
 
264
- { scope: scope }.tap do |opts|
265
- opts[:namespace] = namespace if namespace
266
- opts[:prefix] = prefix if prefix
357
+ association = reflection.build_association(self, instance_options, include_slice)
358
+ y.yield association
359
+ end
267
360
  end
268
361
  end
269
362
 
270
- def serialize(association,options={})
271
- 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)
272
375
  end
376
+ alias to_hash serializable_hash
377
+ alias to_h serializable_hash
273
378
 
274
- def serialize_ids(association)
275
- associated_data = send(association.name)
276
- if associated_data.respond_to?(:to_ary)
277
- associated_data.map { |elem| serialize_id(elem, association) }
278
- else
279
- serialize_id(associated_data, association) if associated_data
280
- end
379
+ # @see #serializable_hash
380
+ def as_json(adapter_opts = nil)
381
+ serializable_hash(adapter_opts)
281
382
  end
282
383
 
283
- def key_format
284
- @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
285
392
  end
286
393
 
287
- def format_key(key)
288
- if key_format == :lower_camel
289
- key.to_s.camelize(:lower)
394
+ def read_attribute_for_serialization(attr)
395
+ if respond_to?(attr)
396
+ send(attr)
290
397
  else
291
- key
398
+ object.read_attribute_for_serialization(attr)
292
399
  end
293
400
  end
294
401
 
295
- def convert_keys(hash)
296
- Hash[hash.map do |k,v|
297
- key = if k.is_a?(Symbol)
298
- format_key(k).to_sym
299
- else
300
- format_key(k)
301
- end
302
-
303
- [key ,v]
304
- end]
305
- end
306
-
307
- attr_writer :serialization_options
308
- def serialization_options
309
- @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
310
411
  end
311
412
 
312
- def serializable_object(options={})
313
- self.serialization_options = options
314
- return @wrap_in_array ? [] : nil if @object.nil?
315
- hash = attributes
316
- hash.merge! associations(options)
317
- hash = convert_keys(hash) if key_format.present?
318
- hash = { :type => type_name(@object), type_name(@object) => hash } if @polymorphic
319
- @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
320
421
  end
321
- alias_method :serializable_hash, :serializable_object
322
422
 
323
- def serialize_id(elem, association)
324
- id = elem.read_attribute_for_serialization(association.embed_key)
325
- association.polymorphic? ? { id: id, type: type_name(elem) } : id
326
- end
423
+ protected
327
424
 
328
- def type_name(elem)
329
- elem.class.to_s.demodulize.underscore.to_sym
330
- end
425
+ attr_accessor :instance_options, :instance_reflections
331
426
  end
332
-
333
427
  end