active_model_serializers 0.10.0 → 0.10.7
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/.rubocop.yml +6 -5
- data/.travis.yml +30 -21
- data/CHANGELOG.md +172 -2
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +23 -4
- data/README.md +166 -28
- data/Rakefile +3 -32
- data/active_model_serializers.gemspec +22 -25
- data/appveyor.yml +10 -6
- data/bin/rubocop +38 -0
- data/docs/README.md +2 -1
- data/docs/general/adapters.md +35 -11
- data/docs/general/caching.md +7 -1
- data/docs/general/configuration_options.md +86 -1
- data/docs/general/deserialization.md +1 -1
- data/docs/general/fields.md +31 -0
- data/docs/general/getting_started.md +1 -1
- data/docs/general/logging.md +7 -0
- data/docs/general/rendering.md +63 -25
- data/docs/general/serializers.md +125 -14
- data/docs/howto/add_pagination_links.md +16 -17
- data/docs/howto/add_relationship_links.md +140 -0
- data/docs/howto/add_root_key.md +11 -0
- data/docs/howto/grape_integration.md +42 -0
- data/docs/howto/outside_controller_use.md +12 -4
- data/docs/howto/passing_arbitrary_options.md +2 -2
- data/docs/howto/serialize_poro.md +46 -5
- data/docs/howto/test.md +2 -0
- data/docs/howto/upgrade_from_0_8_to_0_10.md +265 -0
- data/docs/integrations/ember-and-json-api.md +67 -32
- data/docs/jsonapi/schema.md +1 -1
- data/lib/action_controller/serialization.rb +13 -3
- data/lib/active_model/serializer/adapter/base.rb +2 -0
- data/lib/active_model/serializer/array_serializer.rb +8 -5
- data/lib/active_model/serializer/association.rb +62 -10
- data/lib/active_model/serializer/belongs_to_reflection.rb +4 -3
- data/lib/active_model/serializer/collection_serializer.rb +39 -13
- data/lib/active_model/serializer/{caching.rb → concerns/caching.rb} +82 -115
- data/lib/active_model/serializer/error_serializer.rb +11 -7
- data/lib/active_model/serializer/errors_serializer.rb +25 -20
- data/lib/active_model/serializer/has_many_reflection.rb +3 -3
- data/lib/active_model/serializer/has_one_reflection.rb +1 -4
- data/lib/active_model/serializer/lazy_association.rb +95 -0
- data/lib/active_model/serializer/lint.rb +134 -130
- data/lib/active_model/serializer/reflection.rb +127 -67
- data/lib/active_model/serializer/version.rb +1 -1
- data/lib/active_model/serializer.rb +297 -79
- data/lib/active_model_serializers/adapter/attributes.rb +3 -66
- data/lib/active_model_serializers/adapter/base.rb +39 -39
- data/lib/active_model_serializers/adapter/json_api/deserialization.rb +2 -2
- data/lib/active_model_serializers/adapter/json_api/link.rb +1 -1
- data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +47 -21
- data/lib/active_model_serializers/adapter/json_api/relationship.rb +75 -23
- data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +39 -10
- data/lib/active_model_serializers/adapter/json_api.rb +71 -57
- data/lib/active_model_serializers/adapter.rb +6 -0
- data/lib/active_model_serializers/deprecate.rb +1 -2
- data/lib/active_model_serializers/deserialization.rb +2 -0
- data/lib/active_model_serializers/lookup_chain.rb +80 -0
- data/lib/active_model_serializers/model.rb +109 -28
- data/lib/active_model_serializers/railtie.rb +3 -1
- data/lib/active_model_serializers/register_jsonapi_renderer.rb +44 -31
- data/lib/active_model_serializers/serializable_resource.rb +6 -5
- data/lib/active_model_serializers/serialization_context.rb +10 -3
- data/lib/active_model_serializers/test/schema.rb +2 -2
- data/lib/active_model_serializers.rb +16 -1
- data/lib/generators/rails/resource_override.rb +1 -1
- data/lib/generators/rails/serializer_generator.rb +4 -4
- data/lib/grape/active_model_serializers.rb +7 -5
- data/lib/grape/formatters/active_model_serializers.rb +19 -2
- data/lib/grape/helpers/active_model_serializers.rb +1 -0
- data/lib/tasks/rubocop.rake +53 -0
- data/test/action_controller/adapter_selector_test.rb +14 -5
- data/test/action_controller/explicit_serializer_test.rb +5 -4
- data/test/action_controller/json/include_test.rb +106 -27
- data/test/action_controller/json_api/deserialization_test.rb +1 -1
- data/test/action_controller/json_api/errors_test.rb +8 -9
- data/test/action_controller/json_api/fields_test.rb +66 -0
- data/test/action_controller/json_api/linked_test.rb +29 -24
- data/test/action_controller/json_api/pagination_test.rb +31 -23
- data/test/action_controller/json_api/transform_test.rb +11 -3
- data/test/action_controller/lookup_proc_test.rb +49 -0
- data/test/action_controller/namespace_lookup_test.rb +232 -0
- data/test/action_controller/serialization_scope_name_test.rb +12 -6
- data/test/action_controller/serialization_test.rb +12 -9
- data/test/active_model_serializers/json_pointer_test.rb +15 -13
- data/test/active_model_serializers/model_test.rb +137 -4
- data/test/active_model_serializers/railtie_test_isolated.rb +12 -7
- data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +161 -0
- data/test/active_model_serializers/serialization_context_test_isolated.rb +23 -10
- data/test/active_model_serializers/test/schema_test.rb +3 -2
- data/test/adapter/attributes_test.rb +40 -0
- data/test/adapter/json/collection_test.rb +14 -0
- data/test/adapter/json/has_many_test.rb +10 -2
- data/test/adapter/json/transform_test.rb +15 -15
- data/test/adapter/json_api/collection_test.rb +4 -3
- data/test/adapter/json_api/errors_test.rb +17 -19
- data/test/adapter/json_api/fields_test.rb +12 -3
- data/test/adapter/json_api/has_many_test.rb +49 -20
- data/test/adapter/json_api/include_data_if_sideloaded_test.rb +213 -0
- data/test/adapter/json_api/json_api_test.rb +5 -7
- data/test/adapter/json_api/linked_test.rb +33 -12
- data/test/adapter/json_api/links_test.rb +4 -2
- data/test/adapter/json_api/pagination_links_test.rb +53 -13
- data/test/adapter/json_api/parse_test.rb +1 -1
- data/test/adapter/json_api/relationship_test.rb +309 -73
- data/test/adapter/json_api/resource_meta_test.rb +3 -3
- data/test/adapter/json_api/transform_test.rb +263 -253
- data/test/adapter/json_api/type_test.rb +168 -36
- data/test/adapter/json_test.rb +8 -7
- data/test/adapter/null_test.rb +1 -2
- data/test/adapter/polymorphic_test.rb +52 -5
- data/test/adapter_test.rb +1 -1
- data/test/benchmark/app.rb +1 -1
- data/test/benchmark/benchmarking_support.rb +1 -1
- data/test/benchmark/bm_active_record.rb +81 -0
- data/test/benchmark/bm_adapter.rb +38 -0
- data/test/benchmark/bm_caching.rb +16 -16
- data/test/benchmark/bm_lookup_chain.rb +83 -0
- data/test/benchmark/bm_transform.rb +21 -10
- data/test/benchmark/controllers.rb +16 -17
- data/test/benchmark/fixtures.rb +72 -72
- data/test/cache_test.rb +235 -69
- data/test/collection_serializer_test.rb +31 -14
- data/test/fixtures/active_record.rb +45 -10
- data/test/fixtures/poro.rb +124 -181
- data/test/generators/serializer_generator_test.rb +23 -5
- data/test/grape_test.rb +170 -56
- data/test/lint_test.rb +1 -1
- data/test/logger_test.rb +13 -11
- data/test/serializable_resource_test.rb +18 -22
- data/test/serializers/association_macros_test.rb +3 -2
- data/test/serializers/associations_test.rb +222 -49
- data/test/serializers/attribute_test.rb +5 -3
- data/test/serializers/attributes_test.rb +1 -1
- data/test/serializers/caching_configuration_test_isolated.rb +6 -6
- data/test/serializers/fieldset_test.rb +1 -1
- data/test/serializers/meta_test.rb +12 -6
- data/test/serializers/options_test.rb +17 -6
- data/test/serializers/read_attribute_for_serialization_test.rb +3 -3
- data/test/serializers/reflection_test.rb +427 -0
- data/test/serializers/root_test.rb +1 -1
- data/test/serializers/serialization_test.rb +2 -2
- data/test/serializers/serializer_for_test.rb +12 -10
- data/test/serializers/serializer_for_with_namespace_test.rb +88 -0
- data/test/support/isolated_unit.rb +9 -4
- data/test/support/rails5_shims.rb +8 -2
- data/test/support/rails_app.rb +2 -9
- data/test/support/serialization_testing.rb +31 -5
- data/test/test_helper.rb +13 -0
- metadata +130 -71
- data/.rubocop_todo.yml +0 -167
- data/docs/ARCHITECTURE.md +0 -126
- data/lib/active_model/serializer/associations.rb +0 -100
- data/lib/active_model/serializer/attributes.rb +0 -82
- data/lib/active_model/serializer/collection_reflection.rb +0 -7
- data/lib/active_model/serializer/configuration.rb +0 -35
- data/lib/active_model/serializer/include_tree.rb +0 -111
- data/lib/active_model/serializer/links.rb +0 -35
- data/lib/active_model/serializer/meta.rb +0 -29
- data/lib/active_model/serializer/singular_reflection.rb +0 -7
- data/lib/active_model/serializer/type.rb +0 -25
- data/lib/active_model_serializers/key_transform.rb +0 -70
- data/test/active_model_serializers/key_transform_test.rb +0 -263
- data/test/adapter/json_api/has_many_embed_ids_test.rb +0 -43
- data/test/adapter/json_api/relationships_test.rb +0 -199
- data/test/adapter/json_api/resource_identifier_test.rb +0 -85
- data/test/include_tree/from_include_args_test.rb +0 -26
- data/test/include_tree/from_string_test.rb +0 -94
- data/test/include_tree/include_args_to_hash_test.rb +0 -64
|
@@ -1,50 +1,51 @@
|
|
|
1
1
|
require 'thread_safe'
|
|
2
|
+
require 'jsonapi/include_directive'
|
|
2
3
|
require 'active_model/serializer/collection_serializer'
|
|
3
4
|
require 'active_model/serializer/array_serializer'
|
|
4
5
|
require 'active_model/serializer/error_serializer'
|
|
5
6
|
require 'active_model/serializer/errors_serializer'
|
|
6
|
-
require 'active_model/serializer/
|
|
7
|
-
require 'active_model/serializer/associations'
|
|
8
|
-
require 'active_model/serializer/attributes'
|
|
9
|
-
require 'active_model/serializer/caching'
|
|
10
|
-
require 'active_model/serializer/configuration'
|
|
7
|
+
require 'active_model/serializer/concerns/caching'
|
|
11
8
|
require 'active_model/serializer/fieldset'
|
|
12
9
|
require 'active_model/serializer/lint'
|
|
13
|
-
require 'active_model/serializer/links'
|
|
14
|
-
require 'active_model/serializer/meta'
|
|
15
|
-
require 'active_model/serializer/type'
|
|
16
10
|
|
|
17
11
|
# ActiveModel::Serializer is an abstract class that is
|
|
18
12
|
# reified when subclassed to decorate a resource.
|
|
19
13
|
module ActiveModel
|
|
20
14
|
class Serializer
|
|
15
|
+
undef_method :select, :display # These IO methods, which are mixed into Kernel,
|
|
16
|
+
# sometimes conflict with attribute names. We don't need these IO methods.
|
|
17
|
+
|
|
21
18
|
# @see #serializable_hash for more details on these valid keys.
|
|
22
19
|
SERIALIZABLE_HASH_VALID_KEYS = [:only, :except, :methods, :include, :root].freeze
|
|
23
20
|
extend ActiveSupport::Autoload
|
|
24
21
|
autoload :Adapter
|
|
25
22
|
autoload :Null
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
autoload :Attribute
|
|
24
|
+
autoload :Association
|
|
25
|
+
autoload :Reflection
|
|
26
|
+
autoload :SingularReflection
|
|
27
|
+
autoload :CollectionReflection
|
|
28
|
+
autoload :BelongsToReflection
|
|
29
|
+
autoload :HasOneReflection
|
|
30
|
+
autoload :HasManyReflection
|
|
31
|
+
include ActiveSupport::Configurable
|
|
29
32
|
include Caching
|
|
30
|
-
include Links
|
|
31
|
-
include Meta
|
|
32
|
-
include Type
|
|
33
33
|
|
|
34
34
|
# @param resource [ActiveRecord::Base, ActiveModelSerializers::Model]
|
|
35
35
|
# @return [ActiveModel::Serializer]
|
|
36
36
|
# Preferentially returns
|
|
37
|
-
# 1. resource.
|
|
37
|
+
# 1. resource.serializer_class
|
|
38
38
|
# 2. ArraySerializer when resource is a collection
|
|
39
39
|
# 3. options[:serializer]
|
|
40
40
|
# 4. lookup serializer when resource is a Class
|
|
41
|
-
def self.serializer_for(
|
|
42
|
-
if
|
|
43
|
-
|
|
44
|
-
elsif
|
|
41
|
+
def self.serializer_for(resource_or_class, options = {})
|
|
42
|
+
if resource_or_class.respond_to?(:serializer_class)
|
|
43
|
+
resource_or_class.serializer_class
|
|
44
|
+
elsif resource_or_class.respond_to?(:to_ary)
|
|
45
45
|
config.collection_serializer
|
|
46
46
|
else
|
|
47
|
-
|
|
47
|
+
resource_class = resource_or_class.class == Class ? resource_or_class : resource_or_class.class
|
|
48
|
+
options.fetch(:serializer) { get_serializer_for(resource_class, options[:namespace]) }
|
|
48
49
|
end
|
|
49
50
|
end
|
|
50
51
|
|
|
@@ -59,17 +60,11 @@ module ActiveModel
|
|
|
59
60
|
end
|
|
60
61
|
|
|
61
62
|
# @api private
|
|
62
|
-
def self.serializer_lookup_chain_for(klass)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
serializer_class_name = "#{resource_class_name}Serializer"
|
|
68
|
-
|
|
69
|
-
chain.push("#{name}::#{serializer_class_name}") if self != ActiveModel::Serializer
|
|
70
|
-
chain.push("#{resource_namespace}::#{serializer_class_name}")
|
|
71
|
-
|
|
72
|
-
chain
|
|
63
|
+
def self.serializer_lookup_chain_for(klass, namespace = nil)
|
|
64
|
+
lookups = ActiveModelSerializers.config.serializer_lookup_chain
|
|
65
|
+
Array[*lookups].flat_map do |lookup|
|
|
66
|
+
lookup.call(klass, self, namespace)
|
|
67
|
+
end.compact
|
|
73
68
|
end
|
|
74
69
|
|
|
75
70
|
# Used to cache serializer name => serializer class
|
|
@@ -84,20 +79,229 @@ module ActiveModel
|
|
|
84
79
|
# 1. class name appended with "Serializer"
|
|
85
80
|
# 2. try again with superclass, if present
|
|
86
81
|
# 3. nil
|
|
87
|
-
def self.get_serializer_for(klass)
|
|
82
|
+
def self.get_serializer_for(klass, namespace = nil)
|
|
88
83
|
return nil unless config.serializer_lookup_enabled
|
|
89
|
-
|
|
84
|
+
|
|
85
|
+
cache_key = ActiveSupport::Cache.expand_cache_key(klass, namespace)
|
|
86
|
+
serializers_cache.fetch_or_store(cache_key) do
|
|
90
87
|
# NOTE(beauby): When we drop 1.9.3 support we can lazify the map for perfs.
|
|
91
|
-
|
|
88
|
+
lookup_chain = serializer_lookup_chain_for(klass, namespace)
|
|
89
|
+
serializer_class = lookup_chain.map(&:safe_constantize).find { |x| x && x < ActiveModel::Serializer }
|
|
92
90
|
|
|
93
91
|
if serializer_class
|
|
94
92
|
serializer_class
|
|
95
93
|
elsif klass.superclass
|
|
96
94
|
get_serializer_for(klass.superclass)
|
|
95
|
+
else
|
|
96
|
+
nil # No serializer found
|
|
97
97
|
end
|
|
98
98
|
end
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
+
# @api private
|
|
102
|
+
def self.include_directive_from_options(options)
|
|
103
|
+
if options[:include_directive]
|
|
104
|
+
options[:include_directive]
|
|
105
|
+
elsif options[:include]
|
|
106
|
+
JSONAPI::IncludeDirective.new(options[:include], allow_wildcard: true)
|
|
107
|
+
else
|
|
108
|
+
ActiveModelSerializers.default_include_directive
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @api private
|
|
113
|
+
def self.serialization_adapter_instance
|
|
114
|
+
@serialization_adapter_instance ||= ActiveModelSerializers::Adapter::Attributes
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Preferred interface is ActiveModelSerializers.config
|
|
118
|
+
# BEGIN DEFAULT CONFIGURATION
|
|
119
|
+
config.collection_serializer = ActiveModel::Serializer::CollectionSerializer
|
|
120
|
+
config.serializer_lookup_enabled = true
|
|
121
|
+
|
|
122
|
+
# @deprecated Use {#config.collection_serializer=} instead of this. Is
|
|
123
|
+
# compatibility layer for ArraySerializer.
|
|
124
|
+
def config.array_serializer=(collection_serializer)
|
|
125
|
+
self.collection_serializer = collection_serializer
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# @deprecated Use {#config.collection_serializer} instead of this. Is
|
|
129
|
+
# compatibility layer for ArraySerializer.
|
|
130
|
+
def config.array_serializer
|
|
131
|
+
collection_serializer
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
config.default_includes = '*'
|
|
135
|
+
config.adapter = :attributes
|
|
136
|
+
config.key_transform = nil
|
|
137
|
+
config.jsonapi_pagination_links_enabled = true
|
|
138
|
+
config.jsonapi_resource_type = :plural
|
|
139
|
+
config.jsonapi_namespace_separator = '-'.freeze
|
|
140
|
+
config.jsonapi_version = '1.0'
|
|
141
|
+
config.jsonapi_toplevel_meta = {}
|
|
142
|
+
# Make JSON API top-level jsonapi member opt-in
|
|
143
|
+
# ref: http://jsonapi.org/format/#document-top-level
|
|
144
|
+
config.jsonapi_include_toplevel_object = false
|
|
145
|
+
config.jsonapi_use_foreign_key_on_belongs_to_relationship = false
|
|
146
|
+
config.include_data_default = true
|
|
147
|
+
|
|
148
|
+
# For configuring how serializers are found.
|
|
149
|
+
# This should be an array of procs.
|
|
150
|
+
#
|
|
151
|
+
# The priority of the output is that the first item
|
|
152
|
+
# in the evaluated result array will take precedence
|
|
153
|
+
# over other possible serializer paths.
|
|
154
|
+
#
|
|
155
|
+
# i.e.: First match wins.
|
|
156
|
+
#
|
|
157
|
+
# @example output
|
|
158
|
+
# => [
|
|
159
|
+
# "CustomNamespace::ResourceSerializer",
|
|
160
|
+
# "ParentSerializer::ResourceSerializer",
|
|
161
|
+
# "ResourceNamespace::ResourceSerializer" ,
|
|
162
|
+
# "ResourceSerializer"]
|
|
163
|
+
#
|
|
164
|
+
# If CustomNamespace::ResourceSerializer exists, it will be used
|
|
165
|
+
# for serialization
|
|
166
|
+
config.serializer_lookup_chain = ActiveModelSerializers::LookupChain::DEFAULT.dup
|
|
167
|
+
|
|
168
|
+
config.schema_path = 'test/support/schemas'
|
|
169
|
+
# END DEFAULT CONFIGURATION
|
|
170
|
+
|
|
171
|
+
with_options instance_writer: false, instance_reader: false do |serializer|
|
|
172
|
+
serializer.class_attribute :_attributes_data # @api private
|
|
173
|
+
self._attributes_data ||= {}
|
|
174
|
+
end
|
|
175
|
+
with_options instance_writer: false, instance_reader: true do |serializer|
|
|
176
|
+
serializer.class_attribute :_reflections
|
|
177
|
+
self._reflections ||= {}
|
|
178
|
+
serializer.class_attribute :_links # @api private
|
|
179
|
+
self._links ||= {}
|
|
180
|
+
serializer.class_attribute :_meta # @api private
|
|
181
|
+
serializer.class_attribute :_type # @api private
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def self.inherited(base)
|
|
185
|
+
super
|
|
186
|
+
base._attributes_data = _attributes_data.dup
|
|
187
|
+
base._reflections = _reflections.dup
|
|
188
|
+
base._links = _links.dup
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# @return [Array<Symbol>] Key names of declared attributes
|
|
192
|
+
# @see Serializer::attribute
|
|
193
|
+
def self._attributes
|
|
194
|
+
_attributes_data.keys
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# BEGIN SERIALIZER MACROS
|
|
198
|
+
|
|
199
|
+
# @example
|
|
200
|
+
# class AdminAuthorSerializer < ActiveModel::Serializer
|
|
201
|
+
# attributes :id, :name, :recent_edits
|
|
202
|
+
def self.attributes(*attrs)
|
|
203
|
+
attrs = attrs.first if attrs.first.class == Array
|
|
204
|
+
|
|
205
|
+
attrs.each do |attr|
|
|
206
|
+
attribute(attr)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# @example
|
|
211
|
+
# class AdminAuthorSerializer < ActiveModel::Serializer
|
|
212
|
+
# attributes :id, :recent_edits
|
|
213
|
+
# attribute :name, key: :title
|
|
214
|
+
#
|
|
215
|
+
# attribute :full_name do
|
|
216
|
+
# "#{object.first_name} #{object.last_name}"
|
|
217
|
+
# end
|
|
218
|
+
#
|
|
219
|
+
# def recent_edits
|
|
220
|
+
# object.edits.last(5)
|
|
221
|
+
# end
|
|
222
|
+
def self.attribute(attr, options = {}, &block)
|
|
223
|
+
key = options.fetch(:key, attr)
|
|
224
|
+
_attributes_data[key] = Attribute.new(attr, options, block)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# @param [Symbol] name of the association
|
|
228
|
+
# @param [Hash<Symbol => any>] options for the reflection
|
|
229
|
+
# @return [void]
|
|
230
|
+
#
|
|
231
|
+
# @example
|
|
232
|
+
# has_many :comments, serializer: CommentSummarySerializer
|
|
233
|
+
#
|
|
234
|
+
def self.has_many(name, options = {}, &block) # rubocop:disable Style/PredicateName
|
|
235
|
+
associate(HasManyReflection.new(name, options, block))
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# @param [Symbol] name of the association
|
|
239
|
+
# @param [Hash<Symbol => any>] options for the reflection
|
|
240
|
+
# @return [void]
|
|
241
|
+
#
|
|
242
|
+
# @example
|
|
243
|
+
# belongs_to :author, serializer: AuthorSerializer
|
|
244
|
+
#
|
|
245
|
+
def self.belongs_to(name, options = {}, &block)
|
|
246
|
+
associate(BelongsToReflection.new(name, options, block))
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# @param [Symbol] name of the association
|
|
250
|
+
# @param [Hash<Symbol => any>] options for the reflection
|
|
251
|
+
# @return [void]
|
|
252
|
+
#
|
|
253
|
+
# @example
|
|
254
|
+
# has_one :author, serializer: AuthorSerializer
|
|
255
|
+
#
|
|
256
|
+
def self.has_one(name, options = {}, &block) # rubocop:disable Style/PredicateName
|
|
257
|
+
associate(HasOneReflection.new(name, options, block))
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Add reflection and define {name} accessor.
|
|
261
|
+
# @param [ActiveModel::Serializer::Reflection] reflection
|
|
262
|
+
# @return [void]
|
|
263
|
+
#
|
|
264
|
+
# @api private
|
|
265
|
+
def self.associate(reflection)
|
|
266
|
+
key = reflection.options[:key] || reflection.name
|
|
267
|
+
self._reflections[key] = reflection
|
|
268
|
+
end
|
|
269
|
+
private_class_method :associate
|
|
270
|
+
|
|
271
|
+
# Define a link on a serializer.
|
|
272
|
+
# @example
|
|
273
|
+
# link(:self) { resource_url(object) }
|
|
274
|
+
# @example
|
|
275
|
+
# link(:self) { "http://example.com/resource/#{object.id}" }
|
|
276
|
+
# @example
|
|
277
|
+
# link :resource, "http://example.com/resource"
|
|
278
|
+
#
|
|
279
|
+
def self.link(name, value = nil, &block)
|
|
280
|
+
_links[name] = block || value
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Set the JSON API meta attribute of a serializer.
|
|
284
|
+
# @example
|
|
285
|
+
# class AdminAuthorSerializer < ActiveModel::Serializer
|
|
286
|
+
# meta { stuff: 'value' }
|
|
287
|
+
# @example
|
|
288
|
+
# meta do
|
|
289
|
+
# { comment_count: object.comments.count }
|
|
290
|
+
# end
|
|
291
|
+
def self.meta(value = nil, &block)
|
|
292
|
+
self._meta = block || value
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# Set the JSON API type of a serializer.
|
|
296
|
+
# @example
|
|
297
|
+
# class AdminAuthorSerializer < ActiveModel::Serializer
|
|
298
|
+
# type 'authors'
|
|
299
|
+
def self.type(type)
|
|
300
|
+
self._type = type && type.to_s
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# END SERIALIZER MACROS
|
|
304
|
+
|
|
101
305
|
attr_accessor :object, :root, :scope
|
|
102
306
|
|
|
103
307
|
# `scope_name` is set as :current_user by default in the controller.
|
|
@@ -109,63 +313,58 @@ module ActiveModel
|
|
|
109
313
|
self.root = instance_options[:root]
|
|
110
314
|
self.scope = instance_options[:scope]
|
|
111
315
|
|
|
112
|
-
scope_name = instance_options[:scope_name]
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
end
|
|
316
|
+
return if !(scope_name = instance_options[:scope_name]) || respond_to?(scope_name)
|
|
317
|
+
|
|
318
|
+
define_singleton_method scope_name, -> { scope }
|
|
116
319
|
end
|
|
117
320
|
|
|
118
321
|
def success?
|
|
119
322
|
true
|
|
120
323
|
end
|
|
121
324
|
|
|
325
|
+
# Return the +attributes+ of +object+ as presented
|
|
326
|
+
# by the serializer.
|
|
327
|
+
def attributes(requested_attrs = nil, reload = false)
|
|
328
|
+
@attributes = nil if reload
|
|
329
|
+
@attributes ||= self.class._attributes_data.each_with_object({}) do |(key, attr), hash|
|
|
330
|
+
next if attr.excluded?(self)
|
|
331
|
+
next unless requested_attrs.nil? || requested_attrs.include?(key)
|
|
332
|
+
hash[key] = attr.value(self)
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# @param [JSONAPI::IncludeDirective] include_directive (defaults to the
|
|
337
|
+
# +default_include_directive+ config value when not provided)
|
|
338
|
+
# @return [Enumerator<Association>]
|
|
339
|
+
def associations(include_directive = ActiveModelSerializers.default_include_directive, include_slice = nil)
|
|
340
|
+
include_slice ||= include_directive
|
|
341
|
+
return Enumerator.new {} unless object
|
|
342
|
+
|
|
343
|
+
Enumerator.new do |y|
|
|
344
|
+
self.class._reflections.each do |key, reflection|
|
|
345
|
+
next if reflection.excluded?(self)
|
|
346
|
+
next unless include_directive.key?(key)
|
|
347
|
+
|
|
348
|
+
association = reflection.build_association(self, instance_options, include_slice)
|
|
349
|
+
y.yield association
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
122
354
|
# @return [Hash] containing the attributes and first level
|
|
123
355
|
# associations, similar to how ActiveModel::Serializers::JSON is used
|
|
124
356
|
# in ActiveRecord::Base.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
# (:only, :except, :methods, and :include).
|
|
132
|
-
#
|
|
133
|
-
# See
|
|
134
|
-
# https://github.com/rails/rails/blob/v5.0.0.beta2/activemodel/lib/active_model/serializers/json.rb#L17-L101
|
|
135
|
-
# https://github.com/rails/rails/blob/v5.0.0.beta2/activemodel/lib/active_model/serialization.rb#L85-L123
|
|
136
|
-
# https://github.com/rails/rails/blob/v5.0.0.beta2/activerecord/lib/active_record/serialization.rb#L11-L17
|
|
137
|
-
# https://github.com/rails/rails/blob/v5.0.0.beta2/activesupport/lib/active_support/core_ext/object/json.rb#L147-L162
|
|
138
|
-
#
|
|
139
|
-
# @example
|
|
140
|
-
# # The :only and :except options can be used to limit the attributes included, and work
|
|
141
|
-
# # similar to the attributes method.
|
|
142
|
-
# serializer.as_json(only: [:id, :name])
|
|
143
|
-
# serializer.as_json(except: [:id, :created_at, :age])
|
|
144
|
-
#
|
|
145
|
-
# # To include the result of some method calls on the model use :methods:
|
|
146
|
-
# serializer.as_json(methods: :permalink)
|
|
147
|
-
#
|
|
148
|
-
# # To include associations use :include:
|
|
149
|
-
# serializer.as_json(include: :posts)
|
|
150
|
-
# # Second level and higher order associations work as well:
|
|
151
|
-
# serializer.as_json(include: { posts: { include: { comments: { only: :body } }, only: :title } })
|
|
152
|
-
def serializable_hash(adapter_opts = nil)
|
|
153
|
-
adapter_opts ||= {}
|
|
154
|
-
adapter_opts = { include: '*', adapter: :attributes }.merge!(adapter_opts)
|
|
155
|
-
adapter = ActiveModelSerializers::Adapter.create(self, adapter_opts)
|
|
156
|
-
adapter.serializable_hash(adapter_opts)
|
|
357
|
+
def serializable_hash(adapter_options = nil, options = {}, adapter_instance = self.class.serialization_adapter_instance)
|
|
358
|
+
adapter_options ||= {}
|
|
359
|
+
options[:include_directive] ||= ActiveModel::Serializer.include_directive_from_options(adapter_options)
|
|
360
|
+
resource = attributes_hash(adapter_options, options, adapter_instance)
|
|
361
|
+
relationships = associations_hash(adapter_options, options, adapter_instance)
|
|
362
|
+
resource.merge(relationships)
|
|
157
363
|
end
|
|
158
364
|
alias to_hash serializable_hash
|
|
159
365
|
alias to_h serializable_hash
|
|
160
366
|
|
|
161
367
|
# @see #serializable_hash
|
|
162
|
-
# TODO: When moving attributes adapter logic here, @see #serializable_hash
|
|
163
|
-
# So that the below is true:
|
|
164
|
-
# @param options [nil, Hash] The same valid options passed to `as_json`
|
|
165
|
-
# (:root, :only, :except, :methods, and :include).
|
|
166
|
-
# The default for `root` is nil.
|
|
167
|
-
# The default value for include_root is false. You can change it to true if the given
|
|
168
|
-
# JSON string includes a single root node.
|
|
169
368
|
def as_json(adapter_opts = nil)
|
|
170
369
|
serializable_hash(adapter_opts)
|
|
171
370
|
end
|
|
@@ -178,13 +377,32 @@ module ActiveModel
|
|
|
178
377
|
def read_attribute_for_serialization(attr)
|
|
179
378
|
if respond_to?(attr)
|
|
180
379
|
send(attr)
|
|
181
|
-
elsif self.class._fragmented
|
|
182
|
-
self.class._fragmented.read_attribute_for_serialization(attr)
|
|
183
380
|
else
|
|
184
381
|
object.read_attribute_for_serialization(attr)
|
|
185
382
|
end
|
|
186
383
|
end
|
|
187
384
|
|
|
385
|
+
# @api private
|
|
386
|
+
def attributes_hash(_adapter_options, options, adapter_instance)
|
|
387
|
+
if self.class.cache_enabled?
|
|
388
|
+
fetch_attributes(options[:fields], options[:cached_attributes] || {}, adapter_instance)
|
|
389
|
+
elsif self.class.fragment_cache_enabled?
|
|
390
|
+
fetch_attributes_fragment(adapter_instance, options[:cached_attributes] || {})
|
|
391
|
+
else
|
|
392
|
+
attributes(options[:fields], true)
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# @api private
|
|
397
|
+
def associations_hash(adapter_options, options, adapter_instance)
|
|
398
|
+
include_directive = options.fetch(:include_directive)
|
|
399
|
+
include_slice = options[:include_slice]
|
|
400
|
+
associations(include_directive, include_slice).each_with_object({}) do |association, relationships|
|
|
401
|
+
adapter_opts = adapter_options.merge(include_directive: include_directive[association.key], adapter_instance: adapter_instance)
|
|
402
|
+
relationships[association.key] = association.serializable_hash(adapter_opts, adapter_instance)
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
188
406
|
protected
|
|
189
407
|
|
|
190
408
|
attr_accessor :instance_options
|
|
@@ -1,75 +1,12 @@
|
|
|
1
1
|
module ActiveModelSerializers
|
|
2
2
|
module Adapter
|
|
3
3
|
class Attributes < Base
|
|
4
|
-
def initialize(serializer, options = {})
|
|
5
|
-
super
|
|
6
|
-
@include_tree = ActiveModel::Serializer::IncludeTree.from_include_args(options[:include] || '*')
|
|
7
|
-
@cached_attributes = options[:cache_attributes] || {}
|
|
8
|
-
end
|
|
9
|
-
|
|
10
4
|
def serializable_hash(options = nil)
|
|
11
5
|
options = serialization_options(options)
|
|
6
|
+
options[:fields] ||= instance_options[:fields]
|
|
7
|
+
serialized_hash = serializer.serializable_hash(instance_options, options, self)
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
serializable_hash_for_collection(options)
|
|
15
|
-
else
|
|
16
|
-
serializable_hash_for_single_resource(options)
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
def serializable_hash_for_collection(options)
|
|
23
|
-
cache_attributes
|
|
24
|
-
|
|
25
|
-
serializer.map { |s| Attributes.new(s, instance_options).serializable_hash(options) }
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def serializable_hash_for_single_resource(options)
|
|
29
|
-
resource = resource_object_for(options)
|
|
30
|
-
relationships = resource_relationships(options)
|
|
31
|
-
resource.merge(relationships)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def resource_relationships(options)
|
|
35
|
-
relationships = {}
|
|
36
|
-
serializer.associations(@include_tree).each do |association|
|
|
37
|
-
relationships[association.key] ||= relationship_value_for(association, options)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
relationships
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def relationship_value_for(association, options)
|
|
44
|
-
return association.options[:virtual_value] if association.options[:virtual_value]
|
|
45
|
-
return unless association.serializer && association.serializer.object
|
|
46
|
-
|
|
47
|
-
opts = instance_options.merge(include: @include_tree[association.key])
|
|
48
|
-
relationship_value = Attributes.new(association.serializer, opts).serializable_hash(options)
|
|
49
|
-
|
|
50
|
-
if association.options[:polymorphic] && relationship_value
|
|
51
|
-
polymorphic_type = association.serializer.object.class.name.underscore
|
|
52
|
-
relationship_value = { type: polymorphic_type, polymorphic_type.to_sym => relationship_value }
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
relationship_value
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Set @cached_attributes
|
|
59
|
-
def cache_attributes
|
|
60
|
-
return if @cached_attributes.present?
|
|
61
|
-
|
|
62
|
-
@cached_attributes = ActiveModel::Serializer.cache_read_multi(serializer, self, @include_tree)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def resource_object_for(options)
|
|
66
|
-
if serializer.class.cache_enabled?
|
|
67
|
-
@cached_attributes.fetch(serializer.cache_key(self)) do
|
|
68
|
-
serializer.cached_fields(options[:fields], self)
|
|
69
|
-
end
|
|
70
|
-
else
|
|
71
|
-
serializer.cached_fields(options[:fields], self)
|
|
72
|
-
end
|
|
9
|
+
self.class.transform_key_casing!(serialized_hash, instance_options)
|
|
73
10
|
end
|
|
74
11
|
end
|
|
75
12
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'case_transform'
|
|
2
2
|
|
|
3
3
|
module ActiveModelSerializers
|
|
4
4
|
module Adapter
|
|
@@ -8,6 +8,40 @@ module ActiveModelSerializers
|
|
|
8
8
|
ActiveModelSerializers::Adapter.register(subclass)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
+
# Sets the default transform for the adapter.
|
|
12
|
+
#
|
|
13
|
+
# @return [Symbol] the default transform for the adapter
|
|
14
|
+
def self.default_key_transform
|
|
15
|
+
:unaltered
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Determines the transform to use in order of precedence:
|
|
19
|
+
# adapter option, global config, adapter default.
|
|
20
|
+
#
|
|
21
|
+
# @param options [Object]
|
|
22
|
+
# @return [Symbol] the transform to use
|
|
23
|
+
def self.transform(options)
|
|
24
|
+
return options[:key_transform] if options && options[:key_transform]
|
|
25
|
+
ActiveModelSerializers.config.key_transform || default_key_transform
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Transforms the casing of the supplied value.
|
|
29
|
+
#
|
|
30
|
+
# @param value [Object] the value to be transformed
|
|
31
|
+
# @param options [Object] serializable resource options
|
|
32
|
+
# @return [Symbol] the default transform for the adapter
|
|
33
|
+
def self.transform_key_casing!(value, options)
|
|
34
|
+
CaseTransform.send(transform(options), value)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.cache_key
|
|
38
|
+
@cache_key ||= ActiveModelSerializers::Adapter.registered_name(self)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.fragment_cache(cached_hash, non_cached_hash)
|
|
42
|
+
non_cached_hash.merge cached_hash
|
|
43
|
+
end
|
|
44
|
+
|
|
11
45
|
attr_reader :serializer, :instance_options
|
|
12
46
|
|
|
13
47
|
def initialize(serializer, options = {})
|
|
@@ -15,10 +49,6 @@ module ActiveModelSerializers
|
|
|
15
49
|
@instance_options = options
|
|
16
50
|
end
|
|
17
51
|
|
|
18
|
-
def cached_name
|
|
19
|
-
@cached_name ||= self.class.name.demodulize.underscore
|
|
20
|
-
end
|
|
21
|
-
|
|
22
52
|
# Subclasses that implement this method must first call
|
|
23
53
|
# options = serialization_options(options)
|
|
24
54
|
def serializable_hash(_options = nil)
|
|
@@ -29,14 +59,12 @@ module ActiveModelSerializers
|
|
|
29
59
|
serializable_hash(options)
|
|
30
60
|
end
|
|
31
61
|
|
|
32
|
-
def
|
|
33
|
-
|
|
62
|
+
def cache_key
|
|
63
|
+
self.class.cache_key
|
|
34
64
|
end
|
|
35
65
|
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
yield
|
|
39
|
-
end
|
|
66
|
+
def fragment_cache(cached_hash, non_cached_hash)
|
|
67
|
+
self.class.fragment_cache(cached_hash, non_cached_hash)
|
|
40
68
|
end
|
|
41
69
|
|
|
42
70
|
private
|
|
@@ -50,34 +78,6 @@ module ActiveModelSerializers
|
|
|
50
78
|
def root
|
|
51
79
|
serializer.json_key.to_sym if serializer.json_key
|
|
52
80
|
end
|
|
53
|
-
|
|
54
|
-
class << self
|
|
55
|
-
# Sets the default transform for the adapter.
|
|
56
|
-
#
|
|
57
|
-
# @return [Symbol] the default transform for the adapter
|
|
58
|
-
def default_key_transform
|
|
59
|
-
:unaltered
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Determines the transform to use in order of precedence:
|
|
63
|
-
# adapter option, global config, adapter default.
|
|
64
|
-
#
|
|
65
|
-
# @param options [Object]
|
|
66
|
-
# @return [Symbol] the transform to use
|
|
67
|
-
def transform(options)
|
|
68
|
-
return options[:key_transform] if options && options[:key_transform]
|
|
69
|
-
ActiveModelSerializers.config.key_transform || default_key_transform
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Transforms the casing of the supplied value.
|
|
73
|
-
#
|
|
74
|
-
# @param value [Object] the value to be transformed
|
|
75
|
-
# @param options [Object] serializable resource options
|
|
76
|
-
# @return [Symbol] the default transform for the adapter
|
|
77
|
-
def transform_key_casing!(value, options)
|
|
78
|
-
KeyTransform.send(transform(options), value)
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
end
|
|
@@ -189,7 +189,7 @@ module ActiveModelSerializers
|
|
|
189
189
|
|
|
190
190
|
polymorphic = (options[:polymorphic] || []).include?(assoc_name.to_sym)
|
|
191
191
|
if polymorphic
|
|
192
|
-
hash["#{prefix_key}_type".to_sym] = assoc_data.present? ? assoc_data['type'] : nil
|
|
192
|
+
hash["#{prefix_key}_type".to_sym] = assoc_data.present? ? assoc_data['type'].classify : nil
|
|
193
193
|
end
|
|
194
194
|
|
|
195
195
|
hash
|
|
@@ -205,7 +205,7 @@ module ActiveModelSerializers
|
|
|
205
205
|
# @api private
|
|
206
206
|
def transform_keys(hash, options)
|
|
207
207
|
transform = options[:key_transform] || :underscore
|
|
208
|
-
|
|
208
|
+
CaseTransform.send(transform, hash)
|
|
209
209
|
end
|
|
210
210
|
end
|
|
211
211
|
end
|