Skip to content

Commit d191342

Browse files
committed
Merge pull request #1766 from bf4/serializer_cleanup_4
Move serialization logic into Serializer and CollectionSerializer
2 parents 998ca55 + 7254d34 commit d191342

File tree

12 files changed

+168
-154
lines changed

12 files changed

+168
-154
lines changed

lib/active_model/serializer.rb

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,22 @@ def self.get_serializer_for(klass)
9898
end
9999
end
100100

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+
101117
attr_accessor :object, :root, :scope
102118

103119
# `scope_name` is set as :current_user by default in the controller.
@@ -123,9 +139,7 @@ def success?
123139
# associations, similar to how ActiveModel::Serializers::JSON is used
124140
# in ActiveRecord::Base.
125141
#
126-
# TODO: Move to here the Attributes adapter logic for
127-
# +serializable_hash_for_single_resource(options)+
128-
# and include <tt>ActiveModel::Serializers::JSON</tt>.
142+
# TODO: Include <tt>ActiveModel::Serializers::JSON</tt>.
129143
# So that the below is true:
130144
# @param options [nil, Hash] The same valid options passed to `serializable_hash`
131145
# (:only, :except, :methods, and :include).
@@ -149,11 +163,13 @@ def success?
149163
# serializer.as_json(include: :posts)
150164
# # Second level and higher order associations work as well:
151165
# 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)
166+
def serializable_hash(adapter_options = nil, options = {}, adapter_instance = self.class.serialization_adapter_instance)
167+
adapter_options ||= {}
168+
options[:include_directive] ||= ActiveModel::Serializer.include_directive_from_options(adapter_options)
169+
cached_attributes = adapter_options[:cached_attributes] ||= {}
170+
resource = cached_attributes(options[:fields], cached_attributes, adapter_instance)
171+
relationships = resource_relationships(adapter_options, options, adapter_instance)
172+
resource.merge(relationships)
157173
end
158174
alias to_hash serializable_hash
159175
alias to_h serializable_hash
@@ -185,6 +201,35 @@ def read_attribute_for_serialization(attr)
185201
end
186202
end
187203

204+
# @api private
205+
def resource_relationships(adapter_options, options, adapter_instance)
206+
relationships = {}
207+
include_directive = options.fetch(:include_directive)
208+
associations(include_directive).each do |association|
209+
adapter_opts = adapter_options.merge(include_directive: include_directive[association.key])
210+
relationships[association.key] ||= relationship_value_for(association, adapter_opts, adapter_instance)
211+
end
212+
213+
relationships
214+
end
215+
216+
# @api private
217+
def relationship_value_for(association, adapter_options, adapter_instance)
218+
return association.options[:virtual_value] if association.options[:virtual_value]
219+
association_serializer = association.serializer
220+
association_object = association_serializer && association_serializer.object
221+
return unless association_object
222+
223+
relationship_value = association_serializer.serializable_hash(adapter_options, {}, adapter_instance)
224+
225+
if association.options[:polymorphic] && relationship_value
226+
polymorphic_type = association_object.class.name.underscore
227+
relationship_value = { type: polymorphic_type, polymorphic_type.to_sym => relationship_value }
228+
end
229+
230+
relationship_value
231+
end
232+
188233
protected
189234

190235
attr_accessor :instance_options

lib/active_model/serializer/caching.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ def cache_key(adapter_instance)
318318

319319
parts = []
320320
parts << object_cache_key
321-
parts << adapter_instance.cached_name
321+
parts << adapter_instance.cache_key
322322
parts << self.class._cache_digest unless self.class._skip_digest?
323323
@cache_key = parts.join('/')
324324
end

lib/active_model/serializer/collection_serializer.rb

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,23 @@ def initialize(resources, options = {})
1111
@object = resources
1212
@options = options
1313
@root = options[:root]
14-
serializer_context_class = options.fetch(:serializer_context_class, ActiveModel::Serializer)
15-
@serializers = resources.map do |resource|
16-
serializer_class = options.fetch(:serializer) { serializer_context_class.serializer_for(resource) }
17-
18-
if serializer_class.nil? # rubocop:disable Style/GuardClause
19-
fail NoSerializerError, "No serializer found for resource: #{resource.inspect}"
20-
else
21-
serializer_class.new(resource, options.except(:serializer))
22-
end
23-
end
14+
@serializers = serializers_from_resources
2415
end
2516

2617
def success?
2718
true
2819
end
2920

21+
# @api private
22+
def serializable_hash(adapter_options, options, adapter_instance)
23+
include_directive = ActiveModel::Serializer.include_directive_from_options(adapter_options)
24+
adapter_options[:cached_attributes] ||= ActiveModel::Serializer.cache_read_multi(self, adapter_instance, include_directive)
25+
adapter_opts = adapter_options.merge(include_directive: include_directive)
26+
serializers.map do |serializer|
27+
serializer.serializable_hash(adapter_opts, options, adapter_instance)
28+
end
29+
end
30+
3031
# TODO: unify naming of root, json_key, and _type. Right now, a serializer's
3132
# json_key comes from the root option or the object's model name, by default.
3233
# But, if a dev defines a custom `json_key` method with an explicit value,
@@ -59,6 +60,25 @@ def paginated?
5960
protected
6061

6162
attr_reader :serializers, :options
63+
64+
private
65+
66+
def serializers_from_resources
67+
serializer_context_class = options.fetch(:serializer_context_class, ActiveModel::Serializer)
68+
object.map do |resource|
69+
serializer_from_resource(resource, serializer_context_class, options)
70+
end
71+
end
72+
73+
def serializer_from_resource(resource, serializer_context_class, options)
74+
serializer_class = options.fetch(:serializer) { serializer_context_class.serializer_for(resource) }
75+
76+
if serializer_class.nil? # rubocop:disable Style/GuardClause
77+
fail NoSerializerError, "No serializer found for resource: #{resource.inspect}"
78+
else
79+
serializer_class.new(resource, options.except(:serializer))
80+
end
81+
end
6282
end
6383
end
6484
end

lib/active_model_serializers/adapter.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ def register(name, klass = name)
5151
self
5252
end
5353

54+
def registered_name(adapter_class)
55+
ADAPTER_MAP.key adapter_class
56+
end
57+
5458
# @param adapter [String, Symbol, Class] name to fetch adapter by
5559
# @return [ActiveModelSerializers::Adapter] subclass of Adapter
5660
# @raise [UnknownAdapterError]

lib/active_model_serializers/adapter/attributes.rb

Lines changed: 1 addition & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,9 @@
11
module ActiveModelSerializers
22
module Adapter
33
class Attributes < Base
4-
def initialize(serializer, options = {})
5-
super
6-
@include_directive =
7-
if options[:include_directive]
8-
options[:include_directive]
9-
elsif options[:include]
10-
JSONAPI::IncludeDirective.new(options[:include], allow_wildcard: true)
11-
else
12-
ActiveModelSerializers.default_include_directive
13-
end
14-
end
15-
164
def serializable_hash(options = nil)
175
options = serialization_options(options)
18-
19-
if serializer.respond_to?(:each)
20-
serializable_hash_for_collection(options)
21-
else
22-
serializable_hash_for_single_resource(options)
23-
end
24-
end
25-
26-
private
27-
28-
def serializable_hash_for_collection(options)
29-
instance_options[:cached_attributes] ||= ActiveModel::Serializer.cache_read_multi(serializer, self, @include_directive)
30-
opts = instance_options.merge(include_directive: @include_directive)
31-
serializer.map { |s| Attributes.new(s, opts).serializable_hash(options) }
32-
end
33-
34-
def serializable_hash_for_single_resource(options)
35-
cached_attributes = instance_options[:cached_attributes] || {}
36-
resource = serializer.cached_attributes(options[:fields], cached_attributes, self)
37-
relationships = resource_relationships(options)
38-
resource.merge(relationships)
39-
end
40-
41-
def resource_relationships(options)
42-
relationships = {}
43-
serializer.associations(@include_directive).each do |association|
44-
relationships[association.key] ||= relationship_value_for(association, options)
45-
end
46-
47-
relationships
48-
end
49-
50-
def relationship_value_for(association, options)
51-
return association.options[:virtual_value] if association.options[:virtual_value]
52-
return unless association.serializer && association.serializer.object
53-
54-
opts = instance_options.merge(include_directive: @include_directive[association.key])
55-
relationship_value = Attributes.new(association.serializer, opts).serializable_hash(options)
56-
57-
if association.options[:polymorphic] && relationship_value
58-
polymorphic_type = association.serializer.object.class.name.underscore
59-
relationship_value = { type: polymorphic_type, polymorphic_type.to_sym => relationship_value }
60-
end
61-
62-
relationship_value
6+
serializer.serializable_hash(instance_options, options, self)
637
end
648
end
659
end

lib/active_model_serializers/adapter/base.rb

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,47 @@ def self.inherited(subclass)
88
ActiveModelSerializers::Adapter.register(subclass)
99
end
1010

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+
KeyTransform.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+
1145
attr_reader :serializer, :instance_options
1246

1347
def initialize(serializer, options = {})
1448
@serializer = serializer
1549
@instance_options = options
1650
end
1751

18-
def cached_name
19-
@cached_name ||= self.class.name.demodulize.underscore
20-
end
21-
2252
# Subclasses that implement this method must first call
2353
# options = serialization_options(options)
2454
def serializable_hash(_options = nil)
@@ -29,8 +59,12 @@ def as_json(options = nil)
2959
serializable_hash(options)
3060
end
3161

62+
def cache_key
63+
self.class.cache_key
64+
end
65+
3266
def fragment_cache(cached_hash, non_cached_hash)
33-
non_cached_hash.merge cached_hash
67+
self.class.fragment_cache(cached_hash, non_cached_hash)
3468
end
3569

3670
private
@@ -44,34 +78,6 @@ def serialization_options(options)
4478
def root
4579
serializer.json_key.to_sym if serializer.json_key
4680
end
47-
48-
class << self
49-
# Sets the default transform for the adapter.
50-
#
51-
# @return [Symbol] the default transform for the adapter
52-
def default_key_transform
53-
:unaltered
54-
end
55-
56-
# Determines the transform to use in order of precedence:
57-
# adapter option, global config, adapter default.
58-
#
59-
# @param options [Object]
60-
# @return [Symbol] the transform to use
61-
def transform(options)
62-
return options[:key_transform] if options && options[:key_transform]
63-
ActiveModelSerializers.config.key_transform || default_key_transform
64-
end
65-
66-
# Transforms the casing of the supplied value.
67-
#
68-
# @param value [Object] the value to be transformed
69-
# @param options [Object] serializable resource options
70-
# @return [Symbol] the default transform for the adapter
71-
def transform_key_casing!(value, options)
72-
KeyTransform.send(transform(options), value)
73-
end
74-
end
7581
end
7682
end
7783
end

lib/active_model_serializers/adapter/json_api.rb

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,27 @@ class JsonApi < Base
3131
autoload :Error
3232
autoload :Deserialization
3333

34+
def self.default_key_transform
35+
:dash
36+
end
37+
38+
def self.fragment_cache(cached_hash, non_cached_hash, root = true)
39+
core_cached = cached_hash.first
40+
core_non_cached = non_cached_hash.first
41+
no_root_cache = cached_hash.delete_if { |key, _value| key == core_cached[0] }
42+
no_root_non_cache = non_cached_hash.delete_if { |key, _value| key == core_non_cached[0] }
43+
cached_resource = (core_cached[1]) ? core_cached[1].deep_merge(core_non_cached[1]) : core_non_cached[1]
44+
hash = root ? { root => cached_resource } : cached_resource
45+
46+
hash.deep_merge no_root_non_cache.deep_merge no_root_cache
47+
end
48+
3449
def initialize(serializer, options = {})
3550
super
3651
@include_directive = JSONAPI::IncludeDirective.new(options[:include], allow_wildcard: true)
3752
@fieldset = options[:fieldset] || ActiveModel::Serializer::Fieldset.new(options.delete(:fields))
3853
end
3954

40-
def self.default_key_transform
41-
:dash
42-
end
43-
4455
# {http://jsonapi.org/format/#crud Requests are transactional, i.e. success or failure}
4556
# {http://jsonapi.org/format/#document-top-level data and errors MUST NOT coexist in the same document.}
4657
def serializable_hash(*)
@@ -52,6 +63,11 @@ def serializable_hash(*)
5263
self.class.transform_key_casing!(document, instance_options)
5364
end
5465

66+
def fragment_cache(cached_hash, non_cached_hash)
67+
root = !instance_options.include?(:include)
68+
self.class.fragment_cache(cached_hash, non_cached_hash, root)
69+
end
70+
5571
# {http://jsonapi.org/format/#document-top-level Primary data}
5672
# definition:
5773
# ☐ toplevel_data (required)
@@ -174,18 +190,6 @@ def failure_document
174190
hash
175191
end
176192

177-
def fragment_cache(cached_hash, non_cached_hash)
178-
root = false if instance_options.include?(:include)
179-
core_cached = cached_hash.first
180-
core_non_cached = non_cached_hash.first
181-
no_root_cache = cached_hash.delete_if { |key, _value| key == core_cached[0] }
182-
no_root_non_cache = non_cached_hash.delete_if { |key, _value| key == core_non_cached[0] }
183-
cached_resource = (core_cached[1]) ? core_cached[1].deep_merge(core_non_cached[1]) : core_non_cached[1]
184-
hash = root ? { root => cached_resource } : cached_resource
185-
186-
hash.deep_merge no_root_non_cache.deep_merge no_root_cache
187-
end
188-
189193
protected
190194

191195
attr_reader :fieldset

0 commit comments

Comments
 (0)