active_model_serializers 0.9.9 → 0.10.14

Sign up to get free protection for your applications and to get access to all the features.
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