active_model_serializers 0.8.3 → 0.10.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +726 -6
- data/{MIT-LICENSE.txt → MIT-LICENSE} +3 -2
- data/README.md +194 -545
- data/lib/action_controller/serialization.rb +53 -38
- data/lib/active_model/serializable_resource.rb +13 -0
- data/lib/active_model/serializer/adapter/attributes.rb +17 -0
- data/lib/active_model/serializer/adapter/base.rb +20 -0
- data/lib/active_model/serializer/adapter/json.rb +17 -0
- data/lib/active_model/serializer/adapter/json_api.rb +17 -0
- data/lib/active_model/serializer/adapter/null.rb +17 -0
- data/lib/active_model/serializer/adapter.rb +26 -0
- data/lib/active_model/serializer/array_serializer.rb +14 -0
- data/lib/active_model/serializer/association.rb +73 -0
- data/lib/active_model/serializer/attribute.rb +27 -0
- data/lib/active_model/serializer/belongs_to_reflection.rb +13 -0
- data/lib/active_model/serializer/collection_serializer.rb +99 -0
- data/lib/active_model/serializer/concerns/caching.rb +305 -0
- data/lib/active_model/serializer/error_serializer.rb +16 -0
- data/lib/active_model/serializer/errors_serializer.rb +34 -0
- data/lib/active_model/serializer/field.rb +92 -0
- data/lib/active_model/serializer/fieldset.rb +33 -0
- data/lib/active_model/serializer/has_many_reflection.rb +12 -0
- data/lib/active_model/serializer/has_one_reflection.rb +9 -0
- data/lib/active_model/serializer/lazy_association.rb +99 -0
- data/lib/active_model/serializer/link.rb +23 -0
- data/lib/active_model/serializer/lint.rb +152 -0
- data/lib/active_model/serializer/null.rb +19 -0
- data/lib/active_model/serializer/reflection.rb +212 -0
- data/lib/active_model/serializer/version.rb +7 -0
- data/lib/active_model/serializer.rb +354 -442
- data/lib/active_model_serializers/adapter/attributes.rb +36 -0
- data/lib/active_model_serializers/adapter/base.rb +85 -0
- data/lib/active_model_serializers/adapter/json.rb +23 -0
- data/lib/active_model_serializers/adapter/json_api/deserialization.rb +215 -0
- data/lib/active_model_serializers/adapter/json_api/error.rb +98 -0
- data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +51 -0
- data/lib/active_model_serializers/adapter/json_api/link.rb +85 -0
- data/lib/active_model_serializers/adapter/json_api/meta.rb +39 -0
- data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +94 -0
- data/lib/active_model_serializers/adapter/json_api/relationship.rb +106 -0
- data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +68 -0
- data/lib/active_model_serializers/adapter/json_api.rb +535 -0
- data/lib/active_model_serializers/adapter/null.rb +11 -0
- data/lib/active_model_serializers/adapter.rb +100 -0
- data/lib/active_model_serializers/callbacks.rb +57 -0
- data/lib/active_model_serializers/deprecate.rb +56 -0
- data/lib/active_model_serializers/deserialization.rb +17 -0
- data/lib/active_model_serializers/json_pointer.rb +16 -0
- data/lib/active_model_serializers/logging.rb +124 -0
- data/lib/active_model_serializers/lookup_chain.rb +82 -0
- data/lib/active_model_serializers/model.rb +132 -0
- data/lib/active_model_serializers/railtie.rb +62 -0
- data/lib/active_model_serializers/register_jsonapi_renderer.rb +80 -0
- data/lib/active_model_serializers/serializable_resource.rb +84 -0
- data/lib/active_model_serializers/serialization_context.rb +41 -0
- data/lib/active_model_serializers/test/schema.rb +140 -0
- data/lib/active_model_serializers/test/serializer.rb +127 -0
- data/lib/active_model_serializers/test.rb +9 -0
- data/lib/active_model_serializers.rb +49 -81
- data/lib/generators/rails/USAGE +6 -0
- data/lib/generators/rails/resource_override.rb +12 -0
- data/lib/generators/rails/serializer_generator.rb +38 -0
- data/lib/generators/rails/templates/serializer.rb.erb +8 -0
- data/lib/grape/active_model_serializers.rb +18 -0
- data/lib/grape/formatters/active_model_serializers.rb +34 -0
- data/lib/grape/helpers/active_model_serializers.rb +19 -0
- data/lib/tasks/rubocop.rake +60 -0
- metadata +240 -51
- data/.gitignore +0 -18
- data/.travis.yml +0 -28
- data/DESIGN.textile +0 -586
- data/Gemfile +0 -4
- data/Gemfile.edge +0 -9
- data/Rakefile +0 -18
- data/active_model_serializers.gemspec +0 -24
- data/bench/perf.rb +0 -43
- data/cruft.md +0 -19
- data/lib/active_model/array_serializer.rb +0 -104
- data/lib/active_model/serializer/associations.rb +0 -233
- data/lib/active_model/serializers/version.rb +0 -5
- data/lib/active_record/serializer_override.rb +0 -16
- data/lib/generators/resource_override.rb +0 -13
- data/lib/generators/serializer/USAGE +0 -9
- data/lib/generators/serializer/serializer_generator.rb +0 -42
- data/lib/generators/serializer/templates/serializer.rb +0 -19
- data/test/array_serializer_test.rb +0 -75
- data/test/association_test.rb +0 -592
- data/test/caching_test.rb +0 -96
- data/test/generators_test.rb +0 -85
- data/test/no_serialization_scope_test.rb +0 -34
- data/test/serialization_scope_name_test.rb +0 -67
- data/test/serialization_test.rb +0 -392
- data/test/serializer_support_test.rb +0 -51
- data/test/serializer_test.rb +0 -1465
- data/test/test_fakes.rb +0 -217
- data/test/test_helper.rb +0 -32
@@ -1,515 +1,427 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require '
|
4
|
-
require '
|
5
|
-
|
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.
|
6
14
|
module ActiveModel
|
7
|
-
# Active Model Serializer
|
8
|
-
#
|
9
|
-
# Provides a basic serializer implementation that allows you to easily
|
10
|
-
# control how a given object is going to be serialized. On initialization,
|
11
|
-
# it expects two objects as arguments, a resource and options. For example,
|
12
|
-
# one may do in a controller:
|
13
|
-
#
|
14
|
-
# PostSerializer.new(@post, :scope => current_user).to_json
|
15
|
-
#
|
16
|
-
# The object to be serialized is the +@post+ and the current user is passed
|
17
|
-
# in for authorization purposes.
|
18
|
-
#
|
19
|
-
# We use the scope to check if a given attribute should be serialized or not.
|
20
|
-
# For example, some attributes may only be returned if +current_user+ is the
|
21
|
-
# author of the post:
|
22
|
-
#
|
23
|
-
# class PostSerializer < ActiveModel::Serializer
|
24
|
-
# attributes :title, :body
|
25
|
-
# has_many :comments
|
26
|
-
#
|
27
|
-
# private
|
28
|
-
#
|
29
|
-
# def attributes
|
30
|
-
# hash = super
|
31
|
-
# hash.merge!(:email => post.email) if author?
|
32
|
-
# hash
|
33
|
-
# end
|
34
|
-
#
|
35
|
-
# def author?
|
36
|
-
# post.author == scope
|
37
|
-
# end
|
38
|
-
# end
|
39
|
-
#
|
40
15
|
class Serializer
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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]) }
|
55
51
|
end
|
56
52
|
end
|
57
53
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
class_attribute :_root
|
65
|
-
class_attribute :_embed
|
66
|
-
self._embed = :objects
|
67
|
-
class_attribute :_root_embed
|
68
|
-
|
69
|
-
class_attribute :cache
|
70
|
-
class_attribute :perform_caching
|
71
|
-
|
54
|
+
# @see ActiveModelSerializers::Adapter.lookup
|
55
|
+
# Deprecated
|
56
|
+
def self.adapter
|
57
|
+
ActiveModelSerializers::Adapter.lookup(config.adapter)
|
58
|
+
end
|
72
59
|
class << self
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
end
|
77
|
-
|
78
|
-
# Define attributes to be used in the serialization.
|
79
|
-
def attributes(*attrs)
|
80
|
-
|
81
|
-
self._attributes = _attributes.dup
|
82
|
-
|
83
|
-
attrs.each do |attr|
|
84
|
-
if Hash === attr
|
85
|
-
attr.each {|attr_real, key| attribute attr_real, :key => key }
|
86
|
-
else
|
87
|
-
attribute attr
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def attribute(attr, options={})
|
93
|
-
self._attributes = _attributes.merge(attr.is_a?(Hash) ? attr : {attr => options[:key] || attr.to_s.gsub(/\?$/, '').to_sym})
|
94
|
-
|
95
|
-
attr = attr.keys[0] if attr.is_a? Hash
|
96
|
-
|
97
|
-
unless method_defined?(attr)
|
98
|
-
define_method attr do
|
99
|
-
object.read_attribute_for_serialization(attr.to_sym)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
define_include_method attr
|
104
|
-
|
105
|
-
# protect inheritance chains and open classes
|
106
|
-
# if a serializer inherits from another OR
|
107
|
-
# attributes are added later in a classes lifecycle
|
108
|
-
# poison the cache
|
109
|
-
define_method :_fast_attributes do
|
110
|
-
raise NameError
|
111
|
-
end
|
112
|
-
|
113
|
-
end
|
114
|
-
|
115
|
-
def associate(klass, attrs) #:nodoc:
|
116
|
-
options = attrs.extract_options!
|
117
|
-
self._associations = _associations.dup
|
118
|
-
|
119
|
-
attrs.each do |attr|
|
120
|
-
unless method_defined?(attr)
|
121
|
-
define_method attr do
|
122
|
-
object.send attr
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
define_include_method attr
|
127
|
-
|
128
|
-
self._associations[attr] = klass.refine(attr, options)
|
129
|
-
end
|
130
|
-
end
|
60
|
+
extend ActiveModelSerializers::Deprecate
|
61
|
+
deprecate :adapter, 'ActiveModelSerializers::Adapter.configured_adapter'
|
62
|
+
end
|
131
63
|
|
132
|
-
|
133
|
-
|
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
|
134
71
|
|
135
|
-
|
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
|
136
77
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
141
99
|
end
|
142
100
|
end
|
101
|
+
end
|
143
102
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
end
|
153
|
-
|
154
|
-
# Defines an association in the object should be rendered.
|
155
|
-
#
|
156
|
-
# The serializer object should implement the association name
|
157
|
-
# as a method which should return an object when invoked. If a method
|
158
|
-
# with the association name does not exist, the association name is
|
159
|
-
# dispatched to the serialized object.
|
160
|
-
def has_one(*attrs)
|
161
|
-
associate(Associations::HasOne, attrs)
|
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
|
162
111
|
end
|
112
|
+
end
|
163
113
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
#
|
169
|
-
# The +attributes+ hash looks like this:
|
170
|
-
#
|
171
|
-
# { :name => :string, :age => :integer }
|
172
|
-
#
|
173
|
-
# The +associations+ hash looks like this:
|
174
|
-
# { :posts => { :has_many => :posts } }
|
175
|
-
#
|
176
|
-
# If :key is used:
|
177
|
-
#
|
178
|
-
# class PostsSerializer < ActiveModel::Serializer
|
179
|
-
# has_many :posts, :key => :my_posts
|
180
|
-
# end
|
181
|
-
#
|
182
|
-
# the hash looks like this:
|
183
|
-
#
|
184
|
-
# { :my_posts => { :has_many => :posts }
|
185
|
-
#
|
186
|
-
# This information is extracted from the serializer's model class,
|
187
|
-
# which is provided by +SerializerClass.model_class+.
|
188
|
-
#
|
189
|
-
# The schema method uses the +columns_hash+ and +reflect_on_association+
|
190
|
-
# methods, provided by default by ActiveRecord. You can implement these
|
191
|
-
# methods on your custom models if you want the serializer's schema method
|
192
|
-
# to work.
|
193
|
-
#
|
194
|
-
# TODO: This is currently coupled to Active Record. We need to
|
195
|
-
# figure out a way to decouple those two.
|
196
|
-
def schema
|
197
|
-
klass = model_class
|
198
|
-
columns = klass.columns_hash
|
199
|
-
|
200
|
-
attrs = {}
|
201
|
-
_attributes.each do |name, key|
|
202
|
-
if column = columns[name.to_s]
|
203
|
-
attrs[key] = column.type
|
204
|
-
else
|
205
|
-
# Computed attribute (method on serializer or model). We cannot
|
206
|
-
# infer the type, so we put nil, unless specified in the attribute declaration
|
207
|
-
if name != key
|
208
|
-
attrs[name] = key
|
209
|
-
else
|
210
|
-
attrs[key] = nil
|
211
|
-
end
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
associations = {}
|
216
|
-
_associations.each do |attr, association_class|
|
217
|
-
association = association_class.new(attr, self)
|
218
|
-
|
219
|
-
if model_association = klass.reflect_on_association(association.name)
|
220
|
-
# Real association.
|
221
|
-
associations[association.key] = { model_association.macro => model_association.name }
|
222
|
-
else
|
223
|
-
# Computed association. We could infer has_many vs. has_one from
|
224
|
-
# the association class, but that would make it different from
|
225
|
-
# real associations, which read has_one vs. belongs_to from the
|
226
|
-
# model.
|
227
|
-
associations[association.key] = nil
|
228
|
-
end
|
229
|
-
end
|
114
|
+
# @api private
|
115
|
+
def self.serialization_adapter_instance
|
116
|
+
@serialization_adapter_instance ||= ActiveModelSerializers::Adapter::Attributes
|
117
|
+
end
|
230
118
|
|
231
|
-
|
232
|
-
|
119
|
+
# Preferred interface is ActiveModelSerializers.config
|
120
|
+
# BEGIN DEFAULT CONFIGURATION
|
121
|
+
config.collection_serializer = ActiveModel::Serializer::CollectionSerializer
|
122
|
+
config.serializer_lookup_enabled = true
|
233
123
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
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
|
238
129
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
#
|
245
|
-
def embed(type, options={})
|
246
|
-
self._embed = type
|
247
|
-
self._root_embed = true if options[:include]
|
248
|
-
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
|
249
135
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
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
|
281
171
|
|
282
|
-
|
283
|
-
|
284
|
-
options[:url_options] = controller.url_options
|
172
|
+
config.schema_path = 'test/support/schemas'
|
173
|
+
# END DEFAULT CONFIGURATION
|
285
174
|
|
286
|
-
|
287
|
-
|
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
|
288
186
|
end
|
289
187
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
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
|
294
194
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
end
|
300
|
-
end
|
195
|
+
# @return [Array<Symbol>] Key names of declared attributes
|
196
|
+
# @see Serializer::attribute
|
197
|
+
def self._attributes
|
198
|
+
_attributes_data.keys
|
301
199
|
end
|
302
200
|
|
303
|
-
|
304
|
-
return false if self._root == false
|
201
|
+
# BEGIN SERIALIZER MACROS
|
305
202
|
|
306
|
-
|
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
|
307
208
|
|
308
|
-
|
309
|
-
|
310
|
-
else
|
311
|
-
self._root || class_name
|
209
|
+
attrs.each do |attr|
|
210
|
+
attribute(attr)
|
312
211
|
end
|
313
212
|
end
|
314
213
|
|
315
|
-
|
316
|
-
|
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)
|
317
229
|
end
|
318
230
|
|
319
|
-
|
320
|
-
|
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))
|
321
240
|
end
|
322
241
|
|
323
|
-
|
324
|
-
|
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))
|
325
251
|
end
|
326
252
|
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
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))
|
335
262
|
end
|
336
263
|
|
337
|
-
#
|
338
|
-
#
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
hash.merge!(root => serializable_hash)
|
346
|
-
include_meta hash
|
347
|
-
hash
|
348
|
-
else
|
349
|
-
serializable_hash
|
350
|
-
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
|
351
272
|
end
|
352
|
-
|
353
|
-
|
354
|
-
#
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
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 })
|
363
290
|
end
|
364
291
|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
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
|
369
302
|
end
|
370
303
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
# Make sure that if a special options[:hash] was passed in, we generate
|
379
|
-
# a new unique values hash and don't clobber the original. If the hash
|
380
|
-
# passed in is the same as the current options hash, use the current
|
381
|
-
# unique values.
|
382
|
-
#
|
383
|
-
# TODO: Should passing in a Hash even be public API here?
|
384
|
-
unique_values =
|
385
|
-
if hash = options[:hash]
|
386
|
-
if @options[:hash] == hash
|
387
|
-
@options[:unique_values] ||= {}
|
388
|
-
else
|
389
|
-
{}
|
390
|
-
end
|
391
|
-
else
|
392
|
-
hash = @options[:hash]
|
393
|
-
@options[:unique_values] ||= {}
|
394
|
-
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
|
310
|
+
end
|
395
311
|
|
396
|
-
|
397
|
-
value = options[:value]
|
312
|
+
# END SERIALIZER MACROS
|
398
313
|
|
399
|
-
|
400
|
-
if @options.key?(:include)
|
401
|
-
options[:include] = @options[:include].include?(name)
|
402
|
-
elsif @options.include?(:exclude)
|
403
|
-
options[:include] = !@options[:exclude].include?(name)
|
404
|
-
end
|
405
|
-
end
|
314
|
+
attr_accessor :object, :root, :scope
|
406
315
|
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
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]
|
415
324
|
|
416
|
-
|
325
|
+
return if !(scope_name = instance_options[:scope_name]) || respond_to?(scope_name)
|
417
326
|
|
418
|
-
|
419
|
-
|
327
|
+
define_singleton_method scope_name, -> { scope }
|
328
|
+
end
|
420
329
|
|
421
|
-
|
422
|
-
|
423
|
-
elsif association.embed_in_root? && association.embeddable?
|
424
|
-
merge_association hash, association.root, association.serializables, unique_values
|
425
|
-
end
|
426
|
-
elsif association.embed_objects?
|
427
|
-
node[association.key] = association.serialize
|
428
|
-
end
|
330
|
+
def success?
|
331
|
+
true
|
429
332
|
end
|
430
333
|
|
431
|
-
#
|
432
|
-
#
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
# we want to merge a new list of values.
|
440
|
-
def merge_association(hash, key, serializables, unique_values)
|
441
|
-
already_serialized = (unique_values[key] ||= {})
|
442
|
-
serializable_hashes = (hash[key] ||= [])
|
443
|
-
|
444
|
-
serializables.each do |serializable|
|
445
|
-
unless already_serialized.include? serializable.object
|
446
|
-
already_serialized[serializable.object] = true
|
447
|
-
serializable_hashes << serializable.serializable_hash
|
448
|
-
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)
|
449
342
|
end
|
450
343
|
end
|
451
344
|
|
452
|
-
#
|
453
|
-
#
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
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
|
458
351
|
|
459
|
-
|
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)
|
460
356
|
|
461
|
-
|
462
|
-
|
357
|
+
association = reflection.build_association(self, instance_options, include_slice)
|
358
|
+
y.yield association
|
463
359
|
end
|
464
|
-
|
465
|
-
|
466
|
-
self.class.class_eval method
|
467
|
-
_fast_attributes
|
360
|
+
end
|
468
361
|
end
|
469
362
|
|
470
|
-
#
|
471
|
-
|
472
|
-
|
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)
|
473
375
|
end
|
376
|
+
alias to_hash serializable_hash
|
377
|
+
alias to_h serializable_hash
|
474
378
|
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
return nil if @object.nil?
|
479
|
-
@node = attributes
|
480
|
-
include_associations! if _embed
|
481
|
-
@node
|
379
|
+
# @see #serializable_hash
|
380
|
+
def as_json(adapter_opts = nil)
|
381
|
+
serializable_hash(adapter_opts)
|
482
382
|
end
|
483
383
|
|
484
|
-
|
485
|
-
|
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
|
486
392
|
end
|
487
393
|
|
488
|
-
def
|
489
|
-
|
394
|
+
def read_attribute_for_serialization(attr)
|
395
|
+
if respond_to?(attr)
|
396
|
+
send(attr)
|
397
|
+
else
|
398
|
+
object.read_attribute_for_serialization(attr)
|
399
|
+
end
|
490
400
|
end
|
491
401
|
|
492
|
-
#
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
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
|
497
411
|
end
|
498
|
-
end
|
499
|
-
|
500
|
-
# DefaultSerializer
|
501
|
-
#
|
502
|
-
# Provides a constant interface for all items, particularly
|
503
|
-
# for ArraySerializer.
|
504
|
-
class DefaultSerializer
|
505
|
-
attr_reader :object, :options
|
506
412
|
|
507
|
-
|
508
|
-
|
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
|
509
421
|
end
|
510
422
|
|
511
|
-
|
512
|
-
|
513
|
-
|
423
|
+
protected
|
424
|
+
|
425
|
+
attr_accessor :instance_options, :instance_reflections
|
514
426
|
end
|
515
427
|
end
|