active_model_serializers 0.9.0 → 0.10.12

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 (113) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +679 -9
  3. data/MIT-LICENSE +3 -2
  4. data/README.md +195 -753
  5. data/lib/action_controller/serialization.rb +45 -49
  6. data/lib/active_model/serializable_resource.rb +13 -0
  7. data/lib/active_model/serializer.rb +369 -212
  8. data/lib/active_model/serializer/adapter.rb +26 -0
  9. data/lib/active_model/serializer/adapter/attributes.rb +17 -0
  10. data/lib/active_model/serializer/adapter/base.rb +20 -0
  11. data/lib/active_model/serializer/adapter/json.rb +17 -0
  12. data/lib/active_model/serializer/adapter/json_api.rb +17 -0
  13. data/lib/active_model/serializer/adapter/null.rb +17 -0
  14. data/lib/active_model/serializer/array_serializer.rb +14 -0
  15. data/lib/active_model/serializer/association.rb +73 -0
  16. data/lib/active_model/serializer/attribute.rb +27 -0
  17. data/lib/active_model/serializer/belongs_to_reflection.rb +13 -0
  18. data/lib/active_model/serializer/collection_serializer.rb +90 -0
  19. data/lib/active_model/serializer/concerns/caching.rb +305 -0
  20. data/lib/active_model/serializer/error_serializer.rb +16 -0
  21. data/lib/active_model/serializer/errors_serializer.rb +34 -0
  22. data/lib/active_model/serializer/field.rb +92 -0
  23. data/lib/active_model/serializer/fieldset.rb +33 -0
  24. data/lib/active_model/serializer/has_many_reflection.rb +12 -0
  25. data/lib/active_model/serializer/has_one_reflection.rb +9 -0
  26. data/lib/active_model/serializer/lazy_association.rb +99 -0
  27. data/lib/active_model/serializer/link.rb +23 -0
  28. data/lib/active_model/serializer/lint.rb +152 -0
  29. data/lib/active_model/serializer/null.rb +19 -0
  30. data/lib/active_model/serializer/reflection.rb +212 -0
  31. data/lib/active_model/serializer/version.rb +3 -1
  32. data/lib/active_model_serializers.rb +60 -17
  33. data/lib/active_model_serializers/adapter.rb +100 -0
  34. data/lib/active_model_serializers/adapter/attributes.rb +36 -0
  35. data/lib/active_model_serializers/adapter/base.rb +85 -0
  36. data/lib/active_model_serializers/adapter/json.rb +23 -0
  37. data/lib/active_model_serializers/adapter/json_api.rb +535 -0
  38. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +215 -0
  39. data/lib/active_model_serializers/adapter/json_api/error.rb +98 -0
  40. data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +51 -0
  41. data/lib/active_model_serializers/adapter/json_api/link.rb +85 -0
  42. data/lib/active_model_serializers/adapter/json_api/meta.rb +39 -0
  43. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +90 -0
  44. data/lib/active_model_serializers/adapter/json_api/relationship.rb +106 -0
  45. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +68 -0
  46. data/lib/active_model_serializers/adapter/null.rb +11 -0
  47. data/lib/active_model_serializers/callbacks.rb +57 -0
  48. data/lib/active_model_serializers/deprecate.rb +56 -0
  49. data/lib/active_model_serializers/deserialization.rb +17 -0
  50. data/lib/active_model_serializers/json_pointer.rb +16 -0
  51. data/lib/active_model_serializers/logging.rb +124 -0
  52. data/lib/active_model_serializers/lookup_chain.rb +82 -0
  53. data/lib/active_model_serializers/model.rb +132 -0
  54. data/lib/active_model_serializers/model/caching.rb +25 -0
  55. data/lib/active_model_serializers/railtie.rb +52 -0
  56. data/lib/active_model_serializers/register_jsonapi_renderer.rb +80 -0
  57. data/lib/active_model_serializers/serializable_resource.rb +84 -0
  58. data/lib/active_model_serializers/serialization_context.rb +41 -0
  59. data/lib/active_model_serializers/test.rb +9 -0
  60. data/lib/active_model_serializers/test/schema.rb +140 -0
  61. data/lib/active_model_serializers/test/serializer.rb +127 -0
  62. data/lib/generators/rails/USAGE +6 -0
  63. data/lib/{active_model/serializer/generators → generators/rails}/resource_override.rb +3 -4
  64. data/lib/{active_model/serializer/generators/serializer → generators/rails}/serializer_generator.rb +6 -5
  65. data/lib/{active_model/serializer/generators/serializer/templates/serializer.rb → generators/rails/templates/serializer.rb.erb} +0 -0
  66. data/lib/grape/active_model_serializers.rb +18 -0
  67. data/lib/grape/formatters/active_model_serializers.rb +34 -0
  68. data/lib/grape/helpers/active_model_serializers.rb +19 -0
  69. data/lib/tasks/rubocop.rake +55 -0
  70. metadata +315 -99
  71. data/CONTRIBUTING.md +0 -20
  72. data/DESIGN.textile +0 -586
  73. data/lib/action_controller/serialization_test_case.rb +0 -79
  74. data/lib/active_model/array_serializer.rb +0 -65
  75. data/lib/active_model/default_serializer.rb +0 -32
  76. data/lib/active_model/serializable.rb +0 -40
  77. data/lib/active_model/serializer/associations.rb +0 -102
  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 -10
  83. data/lib/active_model/serializer_support.rb +0 -5
  84. data/test/fixtures/active_record.rb +0 -92
  85. data/test/fixtures/poro.rb +0 -75
  86. data/test/integration/action_controller/serialization_test.rb +0 -287
  87. data/test/integration/action_controller/serialization_test_case_test.rb +0 -61
  88. data/test/integration/active_record/active_record_test.rb +0 -77
  89. data/test/integration/generators/resource_generator_test.rb +0 -26
  90. data/test/integration/generators/scaffold_controller_generator_test.rb +0 -64
  91. data/test/integration/generators/serializer_generator_test.rb +0 -41
  92. data/test/test_app.rb +0 -11
  93. data/test/test_helper.rb +0 -24
  94. data/test/unit/active_model/array_serializer/except_test.rb +0 -18
  95. data/test/unit/active_model/array_serializer/key_format_test.rb +0 -18
  96. data/test/unit/active_model/array_serializer/meta_test.rb +0 -53
  97. data/test/unit/active_model/array_serializer/only_test.rb +0 -18
  98. data/test/unit/active_model/array_serializer/root_test.rb +0 -102
  99. data/test/unit/active_model/array_serializer/scope_test.rb +0 -24
  100. data/test/unit/active_model/array_serializer/serialization_test.rb +0 -199
  101. data/test/unit/active_model/default_serializer_test.rb +0 -13
  102. data/test/unit/active_model/serializer/associations/build_serializer_test.rb +0 -21
  103. data/test/unit/active_model/serializer/associations_test.rb +0 -19
  104. data/test/unit/active_model/serializer/attributes_test.rb +0 -41
  105. data/test/unit/active_model/serializer/config_test.rb +0 -88
  106. data/test/unit/active_model/serializer/filter_test.rb +0 -69
  107. data/test/unit/active_model/serializer/has_many_test.rb +0 -230
  108. data/test/unit/active_model/serializer/has_one_test.rb +0 -207
  109. data/test/unit/active_model/serializer/key_format_test.rb +0 -25
  110. data/test/unit/active_model/serializer/meta_test.rb +0 -39
  111. data/test/unit/active_model/serializer/options_test.rb +0 -15
  112. data/test/unit/active_model/serializer/root_test.rb +0 -117
  113. data/test/unit/active_model/serializer/scope_test.rb +0 -49
@@ -1,79 +1,75 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/core_ext/class/attribute'
4
+ require 'active_model_serializers/serialization_context'
2
5
 
3
6
  module ActionController
4
- # Action Controller Serialization
5
- #
6
- # Overrides render :json to check if the given object implements +active_model_serializer+
7
- # as a method. If so, use the returned serializer instead of calling +to_json+ on the object.
8
- #
9
- # This module also provides a serialization_scope method that allows you to configure the
10
- # +serialization_scope+ of the serializer. Most apps will likely set the +serialization_scope+
11
- # to the current user:
12
- #
13
- # class ApplicationController < ActionController::Base
14
- # serialization_scope :current_user
15
- # end
16
- #
17
- # If you need more complex scope rules, you can simply override the serialization_scope:
18
- #
19
- # class ApplicationController < ActionController::Base
20
- # private
21
- #
22
- # def serialization_scope
23
- # current_user
24
- # end
25
- # end
26
- #
27
7
  module Serialization
28
8
  extend ActiveSupport::Concern
29
9
 
30
10
  include ActionController::Renderers
31
11
 
32
- class << self
33
- attr_accessor :enabled
12
+ module ClassMethods
13
+ def serialization_scope(scope)
14
+ self._serialization_scope = scope
15
+ end
34
16
  end
35
- self.enabled = true
36
17
 
37
18
  included do
38
19
  class_attribute :_serialization_scope
39
20
  self._serialization_scope = :current_user
40
- end
41
21
 
42
- module ClassMethods
43
- def serialization_scope(scope)
44
- self._serialization_scope = scope
45
- end
22
+ attr_writer :namespace_for_serializer
46
23
  end
47
24
 
48
- def _render_option_json(resource, options)
49
- serializer = build_json_serializer(resource, options)
25
+ def namespace_for_serializer
26
+ @namespace_for_serializer ||= namespace_for_class(self.class) unless namespace_for_class(self.class) == Object
27
+ end
50
28
 
51
- if serializer
52
- super(serializer, options)
29
+ def namespace_for_class(klass)
30
+ if Module.method_defined?(:module_parent)
31
+ klass.module_parent
53
32
  else
54
- super
33
+ klass.parent
55
34
  end
56
35
  end
57
36
 
58
- private
37
+ def serialization_scope
38
+ return unless _serialization_scope && respond_to?(_serialization_scope, true)
59
39
 
60
- def default_serializer_options
61
- {}
40
+ send(_serialization_scope)
62
41
  end
63
42
 
64
- def serialization_scope
65
- _serialization_scope = self.class._serialization_scope
66
- send(_serialization_scope) if _serialization_scope && respond_to?(_serialization_scope, true)
67
- end
43
+ def get_serializer(resource, options = {})
44
+ unless use_adapter?
45
+ warn 'ActionController::Serialization#use_adapter? has been removed. '\
46
+ "Please pass 'adapter: false' or see ActiveSupport::SerializableResource.new"
47
+ options[:adapter] = false
48
+ end
49
+
50
+ options.fetch(:namespace) { options[:namespace] = namespace_for_serializer }
68
51
 
69
- def build_json_serializer(resource, options = {})
70
- options = default_serializer_options.merge(options)
52
+ serializable_resource = ActiveModelSerializers::SerializableResource.new(resource, options)
53
+ serializable_resource.serialization_scope ||= options.fetch(:scope) { serialization_scope }
54
+ serializable_resource.serialization_scope_name = options.fetch(:scope_name) { _serialization_scope }
55
+ # For compatibility with the JSON renderer: `json.to_json(options) if json.is_a?(String)`.
56
+ # Otherwise, since `serializable_resource` is not a string, the renderer would call
57
+ # `to_json` on a String and given odd results, such as `"".to_json #=> '""'`
58
+ serializable_resource.adapter.is_a?(String) ? serializable_resource.adapter : serializable_resource
59
+ end
71
60
 
72
- if serializer = options.fetch(:serializer, ActiveModel::Serializer.serializer_for(resource))
73
- options[:scope] = serialization_scope unless options.has_key?(:scope)
74
- options[:resource_name] = controller_name if resource.respond_to?(:to_ary)
61
+ # Deprecated
62
+ def use_adapter?
63
+ true
64
+ end
75
65
 
76
- serializer.new(resource, options)
66
+ [:_render_option_json, :_render_with_renderer_json].each do |renderer_method|
67
+ define_method renderer_method do |resource, options|
68
+ options.fetch(:serialization_context) do
69
+ options[:serialization_context] = ActiveModelSerializers::SerializationContext.new(request, options)
70
+ end
71
+ serializable_resource = get_serializer(resource, options)
72
+ super(serializable_resource, options)
77
73
  end
78
74
  end
79
75
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module ActiveModel
6
+ class SerializableResource
7
+ class << self
8
+ extend ActiveModelSerializers::Deprecate
9
+
10
+ delegate_and_deprecate :new, ActiveModelSerializers::SerializableResource
11
+ end
12
+ end
13
+ end
@@ -1,268 +1,425 @@
1
- require 'active_model/array_serializer'
2
- require 'active_model/serializable'
3
- require 'active_model/serializer/associations'
4
- require 'active_model/serializer/config'
5
-
6
- require 'thread'
7
-
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.
8
14
  module ActiveModel
9
15
  class Serializer
10
- include Serializable
11
-
12
- @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
13
53
 
54
+ # @see ActiveModelSerializers::Adapter.lookup
55
+ # Deprecated
56
+ def self.adapter
57
+ ActiveModelSerializers::Adapter.lookup(config.adapter)
58
+ end
14
59
  class << self
15
- def inherited(base)
16
- base._root = _root
17
- base._attributes = (_attributes || []).dup
18
- base._associations = (_associations || {}).dup
19
- end
60
+ extend ActiveModelSerializers::Deprecate
61
+ deprecate :adapter, 'ActiveModelSerializers::Adapter.configured_adapter'
62
+ end
20
63
 
21
- def setup
22
- @mutex.synchronize do
23
- yield CONFIG
24
- end
25
- 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
26
71
 
27
- EMBED_IN_ROOT_OPTIONS = [
28
- :include,
29
- :embed_in_root,
30
- :embed_in_root_key,
31
- :embed_namespace
32
- ].freeze
33
-
34
- def embed(type, options={})
35
- CONFIG.embed = type
36
- if EMBED_IN_ROOT_OPTIONS.any? { |opt| options[opt].present? }
37
- CONFIG.embed_in_root = true
38
- end
39
- if options[:embed_in_root_key].present?
40
- CONFIG.embed_in_root_key = options[:embed_in_root_key]
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
77
+
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)
97
+ else
98
+ nil # No serializer found
41
99
  end
42
- ActiveSupport::Deprecation.warn <<-WARN
43
- ** Notice: embed is deprecated. **
44
- The use of .embed method on a Serializer will be soon removed, as this should have a global scope and not a class scope.
45
- Please use the global .setup method instead:
46
- ActiveModel::Serializer.setup do |config|
47
- config.embed = :#{type}
48
- config.embed_in_root = #{CONFIG.embed_in_root || false}
49
- end
50
- WARN
51
100
  end
101
+ end
52
102
 
53
- def format_keys(format)
54
- @key_format = format
55
- end
56
- attr_reader :key_format
57
-
58
- if RUBY_VERSION >= '2.0'
59
- def serializer_for(resource)
60
- if resource.respond_to?(:to_ary)
61
- if Object.constants.include?(:ArraySerializer)
62
- ::ArraySerializer
63
- else
64
- ArraySerializer
65
- end
66
- else
67
- begin
68
- Object.const_get "#{resource.class.name}Serializer"
69
- rescue NameError
70
- nil
71
- end
72
- end
73
- 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)
74
109
  else
75
- def serializer_for(resource)
76
- if resource.respond_to?(:to_ary)
77
- if Object.constants.include?(:ArraySerializer)
78
- ::ArraySerializer
79
- else
80
- ArraySerializer
81
- end
82
- else
83
- "#{resource.class.name}Serializer".safe_constantize
84
- end
85
- end
110
+ ActiveModelSerializers.default_include_directive
86
111
  end
112
+ end
87
113
 
88
- attr_accessor :_root, :_attributes, :_associations
89
- alias root _root=
90
- alias root= _root=
114
+ # @api private
115
+ def self.serialization_adapter_instance
116
+ @serialization_adapter_instance ||= ActiveModelSerializers::Adapter::Attributes
117
+ end
91
118
 
92
- def root_name
93
- name.demodulize.underscore.sub(/_serializer$/, '') if name
94
- end
119
+ # Preferred interface is ActiveModelSerializers.config
120
+ # BEGIN DEFAULT CONFIGURATION
121
+ config.collection_serializer = ActiveModel::Serializer::CollectionSerializer
122
+ config.serializer_lookup_enabled = true
95
123
 
96
- def attributes(*attrs)
97
- @_attributes.concat attrs
124
+ # @deprecated Use {#config.collection_serializer=} instead of this. Is
125
+ # compatibility layer for ArraySerializer.
126
+ def config.array_serializer=(collection_serializer)
127
+ self.collection_serializer = collection_serializer
128
+ end
98
129
 
99
- attrs.each do |attr|
100
- define_method attr do
101
- object.read_attribute_for_serialization attr
102
- end unless method_defined?(attr)
103
- end
104
- 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
105
135
 
106
- def has_one(*attrs)
107
- associate(Association::HasOne, *attrs)
108
- 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
+
150
+ # For configuring how serializers are found.
151
+ # This should be an array of procs.
152
+ #
153
+ # The priority of the output is that the first item
154
+ # in the evaluated result array will take precedence
155
+ # over other possible serializer paths.
156
+ #
157
+ # i.e.: First match wins.
158
+ #
159
+ # @example output
160
+ # => [
161
+ # "CustomNamespace::ResourceSerializer",
162
+ # "ParentSerializer::ResourceSerializer",
163
+ # "ResourceNamespace::ResourceSerializer" ,
164
+ # "ResourceSerializer"]
165
+ #
166
+ # If CustomNamespace::ResourceSerializer exists, it will be used
167
+ # for serialization
168
+ config.serializer_lookup_chain = ActiveModelSerializers::LookupChain::DEFAULT.dup
169
+
170
+ config.schema_path = 'test/support/schemas'
171
+ # END DEFAULT CONFIGURATION
172
+
173
+ with_options instance_writer: false, instance_reader: false do |serializer|
174
+ serializer.class_attribute :_attributes_data # @api private
175
+ self._attributes_data ||= {}
176
+ end
177
+ with_options instance_writer: false, instance_reader: true do |serializer|
178
+ serializer.class_attribute :_reflections
179
+ self._reflections ||= {}
180
+ serializer.class_attribute :_links # @api private
181
+ self._links ||= {}
182
+ serializer.class_attribute :_meta # @api private
183
+ serializer.class_attribute :_type # @api private
184
+ end
109
185
 
110
- def has_many(*attrs)
111
- associate(Association::HasMany, *attrs)
112
- end
186
+ def self.inherited(base)
187
+ super
188
+ base._attributes_data = _attributes_data.dup
189
+ base._reflections = _reflections.dup
190
+ base._links = _links.dup
191
+ end
113
192
 
114
- private
193
+ # @return [Array<Symbol>] Key names of declared attributes
194
+ # @see Serializer::attribute
195
+ def self._attributes
196
+ _attributes_data.keys
197
+ end
115
198
 
116
- def associate(klass, *attrs)
117
- options = attrs.extract_options!
199
+ # BEGIN SERIALIZER MACROS
118
200
 
119
- attrs.each do |attr|
120
- define_method attr do
121
- object.send attr
122
- end unless method_defined?(attr)
201
+ # @example
202
+ # class AdminAuthorSerializer < ActiveModel::Serializer
203
+ # attributes :id, :name, :recent_edits
204
+ def self.attributes(*attrs)
205
+ attrs = attrs.first if attrs.first.class == Array
123
206
 
124
- @_associations[attr] = klass.new(attr, options)
125
- end
207
+ attrs.each do |attr|
208
+ attribute(attr)
126
209
  end
127
210
  end
128
211
 
129
- def initialize(object, options={})
130
- @object = object
131
- @scope = options[:scope]
132
- @root = options.fetch(:root, self.class._root)
133
- @meta_key = options[:meta_key] || :meta
134
- @meta = options[@meta_key]
135
- @wrap_in_array = options[:_wrap_in_array]
136
- @only = options[:only] ? Array(options[:only]) : nil
137
- @except = options[:except] ? Array(options[:except]) : nil
138
- @key_format = options[:key_format]
139
- @context = options[:context]
212
+ # @example
213
+ # class AdminAuthorSerializer < ActiveModel::Serializer
214
+ # attributes :id, :recent_edits
215
+ # attribute :name, key: :title
216
+ #
217
+ # attribute :full_name do
218
+ # "#{object.first_name} #{object.last_name}"
219
+ # end
220
+ #
221
+ # def recent_edits
222
+ # object.edits.last(5)
223
+ # end
224
+ def self.attribute(attr, options = {}, &block)
225
+ key = options.fetch(:key, attr)
226
+ _attributes_data[key] = Attribute.new(attr, options, block)
140
227
  end
141
- attr_accessor :object, :scope, :root, :meta_key, :meta, :key_format, :context
142
228
 
143
- def json_key
144
- key = if root == true || root.nil?
145
- self.class.root_name
146
- else
147
- root
148
- end
229
+ # @param [Symbol] name of the association
230
+ # @param [Hash<Symbol => any>] options for the reflection
231
+ # @return [void]
232
+ #
233
+ # @example
234
+ # has_many :comments, serializer: CommentSummarySerializer
235
+ #
236
+ def self.has_many(name, options = {}, &block) # rubocop:disable Style/PredicateName
237
+ associate(HasManyReflection.new(name, options, block))
238
+ end
149
239
 
150
- key_format == :lower_camel && key.present? ? key.camelize(:lower) : key
240
+ # @param [Symbol] name of the association
241
+ # @param [Hash<Symbol => any>] options for the reflection
242
+ # @return [void]
243
+ #
244
+ # @example
245
+ # belongs_to :author, serializer: AuthorSerializer
246
+ #
247
+ def self.belongs_to(name, options = {}, &block)
248
+ associate(BelongsToReflection.new(name, options, block))
151
249
  end
152
250
 
153
- def attributes
154
- filter(self.class._attributes.dup).each_with_object({}) do |name, hash|
155
- hash[name] = send(name)
156
- end
251
+ # @param [Symbol] name of the association
252
+ # @param [Hash<Symbol => any>] options for the reflection
253
+ # @return [void]
254
+ #
255
+ # @example
256
+ # has_one :author, serializer: AuthorSerializer
257
+ #
258
+ def self.has_one(name, options = {}, &block) # rubocop:disable Style/PredicateName
259
+ associate(HasOneReflection.new(name, options, block))
157
260
  end
158
261
 
159
- def associations
160
- associations = self.class._associations
161
- included_associations = filter(associations.keys)
162
- associations.each_with_object({}) do |(name, association), hash|
163
- if included_associations.include? name
164
- if association.embed_ids?
165
- ids = serialize_ids association
166
- if association.embed_namespace?
167
- hash = hash[association.embed_namespace] ||= {}
168
- hash[association.key] = ids
169
- else
170
- hash[association.key] = ids
171
- end
172
- elsif association.embed_objects?
173
- if association.embed_namespace?
174
- hash = hash[association.embed_namespace] ||= {}
175
- end
176
- hash[association.embedded_key] = serialize association
177
- end
178
- end
179
- end
262
+ # Add reflection and define {name} accessor.
263
+ # @param [ActiveModel::Serializer::Reflection] reflection
264
+ # @return [void]
265
+ #
266
+ # @api private
267
+ def self.associate(reflection)
268
+ key = reflection.options[:key] || reflection.name
269
+ self._reflections[key] = reflection
270
+ end
271
+ private_class_method :associate
272
+
273
+ # Define a link on a serializer.
274
+ # @example
275
+ # link(:self) { resource_url(object) }
276
+ # @example
277
+ # link(:self) { "http://example.com/resource/#{object.id}" }
278
+ # @example
279
+ # link :resource, "http://example.com/resource"
280
+ # @example
281
+ # link(:callback, if: :internal?), { "http://example.com/callback" }
282
+ #
283
+ def self.link(name, *args, &block)
284
+ options = args.extract_options!
285
+ # For compatibility with the use case of passing link directly as string argument
286
+ # without block, we are creating a wrapping block
287
+ _links[name] = Link.new(name, options, block || ->(_serializer) { args.first })
180
288
  end
181
289
 
182
- def filter(keys)
183
- if @only
184
- keys & @only
185
- elsif @except
186
- keys - @except
187
- else
188
- keys
189
- end
290
+ # Set the JSON API meta attribute of a serializer.
291
+ # @example
292
+ # class AdminAuthorSerializer < ActiveModel::Serializer
293
+ # meta { stuff: 'value' }
294
+ # @example
295
+ # meta do
296
+ # { comment_count: object.comments.count }
297
+ # end
298
+ def self.meta(value = nil, &block)
299
+ self._meta = block || value
190
300
  end
191
301
 
192
- def embedded_in_root_associations
193
- associations = self.class._associations
194
- included_associations = filter(associations.keys)
195
- associations.each_with_object({}) do |(name, association), hash|
196
- if included_associations.include? name
197
- if association.embed_in_root?
198
- if association.embed_in_root_key?
199
- hash = hash[association.embed_in_root_key] ||= {}
200
- end
201
- association_serializer = build_serializer(association)
202
- hash.merge!(association_serializer.embedded_in_root_associations) {|key, oldval, newval| [newval, oldval].flatten }
203
-
204
- serialized_data = association_serializer.serializable_object
205
- key = association.root_key
206
- if hash.has_key?(key)
207
- hash[key].concat(serialized_data).uniq!
208
- else
209
- hash[key] = serialized_data
210
- end
211
- end
212
- end
213
- end
302
+ # Set the JSON API type of a serializer.
303
+ # @example
304
+ # class AdminAuthorSerializer < ActiveModel::Serializer
305
+ # type 'authors'
306
+ def self.type(type)
307
+ self._type = type && type.to_s
214
308
  end
215
309
 
216
- def build_serializer(association)
217
- object = send(association.name)
218
- association.build_serializer(object, scope: scope)
310
+ # END SERIALIZER MACROS
311
+
312
+ attr_accessor :object, :root, :scope
313
+
314
+ # `scope_name` is set as :current_user by default in the controller.
315
+ # If the instance does not have a method named `scope_name`, it
316
+ # defines the method so that it calls the +scope+.
317
+ def initialize(object, options = {})
318
+ self.object = object
319
+ self.instance_options = options
320
+ self.root = instance_options[:root]
321
+ self.scope = instance_options[:scope]
322
+
323
+ return if !(scope_name = instance_options[:scope_name]) || respond_to?(scope_name)
324
+
325
+ define_singleton_method scope_name, -> { scope }
219
326
  end
220
327
 
221
- def serialize(association)
222
- build_serializer(association).serializable_object
328
+ def success?
329
+ true
223
330
  end
224
331
 
225
- def serialize_ids(association)
226
- associated_data = send(association.name)
227
- if associated_data.respond_to?(:to_ary)
228
- associated_data.map { |elem| elem.read_attribute_for_serialization(association.embed_key) }
229
- else
230
- associated_data.read_attribute_for_serialization(association.embed_key) if associated_data
332
+ # Return the +attributes+ of +object+ as presented
333
+ # by the serializer.
334
+ def attributes(requested_attrs = nil, reload = false)
335
+ @attributes = nil if reload
336
+ @attributes ||= self.class._attributes_data.each_with_object({}) do |(key, attr), hash|
337
+ next if attr.excluded?(self)
338
+ next unless requested_attrs.nil? || requested_attrs.include?(key)
339
+ hash[key] = attr.value(self)
231
340
  end
232
341
  end
233
342
 
234
- def key_format
235
- @key_format || self.class.key_format || CONFIG.key_format
343
+ # @param [JSONAPI::IncludeDirective] include_directive (defaults to the
344
+ # +default_include_directive+ config value when not provided)
345
+ # @return [Enumerator<Association>]
346
+ def associations(include_directive = ActiveModelSerializers.default_include_directive, include_slice = nil)
347
+ include_slice ||= include_directive
348
+ return Enumerator.new {} unless object
349
+
350
+ Enumerator.new do |y|
351
+ (self.instance_reflections ||= self.class._reflections.deep_dup).each do |key, reflection|
352
+ next if reflection.excluded?(self)
353
+ next unless include_directive.key?(key)
354
+
355
+ association = reflection.build_association(self, instance_options, include_slice)
356
+ y.yield association
357
+ end
358
+ end
236
359
  end
237
360
 
238
- def format_key(key)
239
- if key_format == :lower_camel
240
- key.to_s.camelize(:lower)
241
- else
242
- key
361
+ # @return [Hash] containing the attributes and first level
362
+ # associations, similar to how ActiveModel::Serializers::JSON is used
363
+ # in ActiveRecord::Base.
364
+ def serializable_hash(adapter_options = nil, options = {}, adapter_instance = self.class.serialization_adapter_instance)
365
+ adapter_options ||= {}
366
+ options[:include_directive] ||= ActiveModel::Serializer.include_directive_from_options(adapter_options)
367
+ if (fieldset = adapter_options[:fieldset])
368
+ options[:fields] = fieldset.fields_for(json_key)
243
369
  end
370
+ resource = attributes_hash(adapter_options, options, adapter_instance)
371
+ relationships = associations_hash(adapter_options, options, adapter_instance)
372
+ resource.merge(relationships)
244
373
  end
374
+ alias to_hash serializable_hash
375
+ alias to_h serializable_hash
245
376
 
246
- def convert_keys(hash)
247
- Hash[hash.map do |k,v|
248
- key = if k.is_a?(Symbol)
249
- format_key(k).to_sym
250
- else
251
- format_key(k)
377
+ # @see #serializable_hash
378
+ def as_json(adapter_opts = nil)
379
+ serializable_hash(adapter_opts)
380
+ end
381
+
382
+ # Used by adapter as resource root.
383
+ def json_key
384
+ root || _type ||
385
+ begin
386
+ object.class.model_name.to_s.underscore
387
+ rescue ArgumentError
388
+ 'anonymous_object'
252
389
  end
390
+ end
391
+
392
+ def read_attribute_for_serialization(attr)
393
+ if respond_to?(attr)
394
+ send(attr)
395
+ else
396
+ object.read_attribute_for_serialization(attr)
397
+ end
398
+ end
253
399
 
254
- [key ,v]
255
- end]
400
+ # @api private
401
+ def attributes_hash(_adapter_options, options, adapter_instance)
402
+ if self.class.cache_enabled?
403
+ fetch_attributes(options[:fields], options[:cached_attributes] || {}, adapter_instance)
404
+ elsif self.class.fragment_cache_enabled?
405
+ fetch_attributes_fragment(adapter_instance, options[:cached_attributes] || {})
406
+ else
407
+ attributes(options[:fields], true)
408
+ end
256
409
  end
257
410
 
258
- def serializable_object(options={})
259
- return @wrap_in_array ? [] : nil if @object.nil?
260
- hash = attributes
261
- hash.merge! associations
262
- hash = convert_keys(hash) if key_format.present?
263
- @wrap_in_array ? [hash] : hash
411
+ # @api private
412
+ def associations_hash(adapter_options, options, adapter_instance)
413
+ include_directive = options.fetch(:include_directive)
414
+ include_slice = options[:include_slice]
415
+ associations(include_directive, include_slice).each_with_object({}) do |association, relationships|
416
+ adapter_opts = adapter_options.merge(include_directive: include_directive[association.key], adapter_instance: adapter_instance)
417
+ relationships[association.key] = association.serializable_hash(adapter_opts, adapter_instance)
418
+ end
264
419
  end
265
- alias_method :serializable_hash, :serializable_object
266
- end
267
420
 
421
+ protected
422
+
423
+ attr_accessor :instance_options, :instance_reflections
424
+ end
268
425
  end