active_model_serializers 0.8.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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +702 -6
- data/{MIT-LICENSE.txt → MIT-LICENSE} +3 -2
- data/README.md +195 -536
- data/lib/action_controller/serialization.rb +53 -35
- 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 +90 -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 +352 -441
- 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 +90 -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/caching.rb +25 -0
- data/lib/active_model_serializers/model.rb +132 -0
- data/lib/active_model_serializers/railtie.rb +52 -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 -83
- 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 +55 -0
- metadata +291 -77
- data/.gitignore +0 -18
- data/.travis.yml +0 -29
- data/DESIGN.textile +0 -586
- data/Gemfile +0 -6
- data/Gemfile.edge +0 -9
- data/Rakefile +0 -18
- data/active_model_serializers.gemspec +0 -25
- 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 -54
- 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 -394
- data/test/serializer_support_test.rb +0 -51
- data/test/serializer_test.rb +0 -1452
- data/test/test_fakes.rb +0 -194
- data/test/test_helper.rb +0 -41
|
@@ -1,514 +1,425 @@
|
|
|
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
|
+
|
|
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
|
|
281
169
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
options[:url_options] = controller.url_options
|
|
170
|
+
config.schema_path = 'test/support/schemas'
|
|
171
|
+
# END DEFAULT CONFIGURATION
|
|
285
172
|
|
|
286
|
-
|
|
287
|
-
|
|
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
|
|
288
184
|
end
|
|
289
185
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
|
294
192
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
end
|
|
300
|
-
end
|
|
193
|
+
# @return [Array<Symbol>] Key names of declared attributes
|
|
194
|
+
# @see Serializer::attribute
|
|
195
|
+
def self._attributes
|
|
196
|
+
_attributes_data.keys
|
|
301
197
|
end
|
|
302
198
|
|
|
303
|
-
|
|
304
|
-
return false if self._root == false
|
|
199
|
+
# BEGIN SERIALIZER MACROS
|
|
305
200
|
|
|
306
|
-
|
|
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
|
|
307
206
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
else
|
|
311
|
-
self._root || class_name
|
|
207
|
+
attrs.each do |attr|
|
|
208
|
+
attribute(attr)
|
|
312
209
|
end
|
|
313
210
|
end
|
|
314
211
|
|
|
315
|
-
|
|
316
|
-
|
|
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)
|
|
317
227
|
end
|
|
318
228
|
|
|
319
|
-
|
|
320
|
-
|
|
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))
|
|
321
238
|
end
|
|
322
239
|
|
|
323
|
-
|
|
324
|
-
|
|
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))
|
|
325
249
|
end
|
|
326
250
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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))
|
|
335
260
|
end
|
|
336
261
|
|
|
337
|
-
#
|
|
338
|
-
#
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
include_meta hash
|
|
346
|
-
hash
|
|
347
|
-
else
|
|
348
|
-
serializable_hash
|
|
349
|
-
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
|
|
350
270
|
end
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
#
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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 })
|
|
362
288
|
end
|
|
363
289
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
|
368
300
|
end
|
|
369
301
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
# Make sure that if a special options[:hash] was passed in, we generate
|
|
378
|
-
# a new unique values hash and don't clobber the original. If the hash
|
|
379
|
-
# passed in is the same as the current options hash, use the current
|
|
380
|
-
# unique values.
|
|
381
|
-
#
|
|
382
|
-
# TODO: Should passing in a Hash even be public API here?
|
|
383
|
-
unique_values =
|
|
384
|
-
if hash = options[:hash]
|
|
385
|
-
if @options[:hash] == hash
|
|
386
|
-
@options[:unique_values] ||= {}
|
|
387
|
-
else
|
|
388
|
-
{}
|
|
389
|
-
end
|
|
390
|
-
else
|
|
391
|
-
hash = @options[:hash]
|
|
392
|
-
@options[:unique_values] ||= {}
|
|
393
|
-
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
|
|
308
|
+
end
|
|
394
309
|
|
|
395
|
-
|
|
396
|
-
value = options[:value]
|
|
310
|
+
# END SERIALIZER MACROS
|
|
397
311
|
|
|
398
|
-
|
|
399
|
-
if @options.key?(:include)
|
|
400
|
-
options[:include] = @options[:include].include?(name)
|
|
401
|
-
elsif @options.include?(:exclude)
|
|
402
|
-
options[:include] = !@options[:exclude].include?(name)
|
|
403
|
-
end
|
|
404
|
-
end
|
|
312
|
+
attr_accessor :object, :root, :scope
|
|
405
313
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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]
|
|
414
322
|
|
|
415
|
-
|
|
323
|
+
return if !(scope_name = instance_options[:scope_name]) || respond_to?(scope_name)
|
|
416
324
|
|
|
417
|
-
|
|
418
|
-
|
|
325
|
+
define_singleton_method scope_name, -> { scope }
|
|
326
|
+
end
|
|
419
327
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
elsif association.embed_in_root? && association.embeddable?
|
|
423
|
-
merge_association hash, association.root, association.serializables, unique_values
|
|
424
|
-
end
|
|
425
|
-
elsif association.embed_objects?
|
|
426
|
-
node[association.key] = association.serialize
|
|
427
|
-
end
|
|
328
|
+
def success?
|
|
329
|
+
true
|
|
428
330
|
end
|
|
429
331
|
|
|
430
|
-
#
|
|
431
|
-
#
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
# we want to merge a new list of values.
|
|
439
|
-
def merge_association(hash, key, serializables, unique_values)
|
|
440
|
-
already_serialized = (unique_values[key] ||= {})
|
|
441
|
-
serializable_hashes = (hash[key] ||= [])
|
|
442
|
-
|
|
443
|
-
serializables.each do |serializable|
|
|
444
|
-
unless already_serialized.include? serializable.object
|
|
445
|
-
already_serialized[serializable.object] = true
|
|
446
|
-
serializable_hashes << serializable.serializable_hash
|
|
447
|
-
end
|
|
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)
|
|
448
340
|
end
|
|
449
341
|
end
|
|
450
342
|
|
|
451
|
-
#
|
|
452
|
-
#
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
|
457
349
|
|
|
458
|
-
|
|
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)
|
|
459
354
|
|
|
460
|
-
|
|
461
|
-
|
|
355
|
+
association = reflection.build_association(self, instance_options, include_slice)
|
|
356
|
+
y.yield association
|
|
462
357
|
end
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
self.class.class_eval method
|
|
466
|
-
_fast_attributes
|
|
358
|
+
end
|
|
467
359
|
end
|
|
468
360
|
|
|
469
|
-
#
|
|
470
|
-
|
|
471
|
-
|
|
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)
|
|
369
|
+
end
|
|
370
|
+
resource = attributes_hash(adapter_options, options, adapter_instance)
|
|
371
|
+
relationships = associations_hash(adapter_options, options, adapter_instance)
|
|
372
|
+
resource.merge(relationships)
|
|
472
373
|
end
|
|
374
|
+
alias to_hash serializable_hash
|
|
375
|
+
alias to_h serializable_hash
|
|
473
376
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
return nil if @object.nil?
|
|
478
|
-
@node = attributes
|
|
479
|
-
include_associations! if _embed
|
|
480
|
-
@node
|
|
377
|
+
# @see #serializable_hash
|
|
378
|
+
def as_json(adapter_opts = nil)
|
|
379
|
+
serializable_hash(adapter_opts)
|
|
481
380
|
end
|
|
482
381
|
|
|
483
|
-
|
|
484
|
-
|
|
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'
|
|
389
|
+
end
|
|
485
390
|
end
|
|
486
391
|
|
|
487
|
-
def
|
|
488
|
-
|
|
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
|
|
489
398
|
end
|
|
490
399
|
|
|
491
|
-
#
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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
|
|
496
409
|
end
|
|
497
|
-
end
|
|
498
|
-
|
|
499
|
-
# DefaultSerializer
|
|
500
|
-
#
|
|
501
|
-
# Provides a constant interface for all items, particularly
|
|
502
|
-
# for ArraySerializer.
|
|
503
|
-
class DefaultSerializer
|
|
504
|
-
attr_reader :object, :options
|
|
505
410
|
|
|
506
|
-
|
|
507
|
-
|
|
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
|
|
508
419
|
end
|
|
509
420
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
421
|
+
protected
|
|
422
|
+
|
|
423
|
+
attr_accessor :instance_options, :instance_reflections
|
|
513
424
|
end
|
|
514
425
|
end
|