Skip to content

Commit 366012c

Browse files
committed
New syntax for includes with ActiveModel::Serializer::utils.include_args_to_hash.
1 parent 4399c1a commit 366012c

File tree

11 files changed

+347
-198
lines changed

11 files changed

+347
-198
lines changed

lib/active_model/serializer.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class Serializer
1010
autoload :Lint
1111
autoload :Associations
1212
autoload :Fieldset
13+
autoload :Utils
1314
include Configuration
1415
include Associations
1516

Lines changed: 162 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -1,178 +1,164 @@
11
class ActiveModel::Serializer::Adapter::JsonApi < ActiveModel::Serializer::Adapter
2-
extend ActiveSupport::Autoload
3-
autoload :PaginationLinks
4-
autoload :FragmentCache
5-
6-
def initialize(serializer, options = {})
7-
super
8-
@hash = { data: [] }
9-
10-
@options[:include] ||= []
11-
if @options[:include].is_a?(String)
12-
@options[:include] = @options[:include].split(',')
13-
end
14-
15-
fields = options.delete(:fields)
16-
if fields
17-
@fieldset = ActiveModel::Serializer::Fieldset.new(fields, serializer.json_key)
18-
else
19-
@fieldset = options[:fieldset]
20-
end
21-
end
22-
23-
def serializable_hash(options = nil)
24-
options ||= {}
25-
if serializer.respond_to?(:each)
26-
serializer.each do |s|
27-
result = self.class.new(s, @options.merge(fieldset: @fieldset)).serializable_hash(options)
28-
@hash[:data] << result[:data]
29-
30-
if result[:included]
31-
@hash[:included] ||= []
32-
@hash[:included] |= result[:included]
33-
end
34-
end
35-
36-
add_links(options)
37-
else
38-
primary_data = primary_data_for(serializer, options)
39-
relationships = relationships_for(serializer)
40-
included = included_for(serializer)
41-
@hash[:data] = primary_data
42-
@hash[:data][:relationships] = relationships if relationships.any?
43-
@hash[:included] = included if included.any?
44-
end
45-
@hash
46-
end
47-
48-
def fragment_cache(cached_hash, non_cached_hash)
49-
root = false if @options.include?(:include)
50-
ActiveModel::Serializer::Adapter::JsonApi::FragmentCache.new().fragment_cache(root, cached_hash, non_cached_hash)
51-
end
52-
53-
private
54-
55-
def resource_identifier_type_for(serializer)
56-
if ActiveModel::Serializer.config.jsonapi_resource_type == :singular
57-
serializer.object.class.model_name.singular
58-
else
59-
serializer.object.class.model_name.plural
60-
end
61-
end
62-
63-
def add_relationships(resource, name, serializers)
64-
resource[:relationships] ||= {}
65-
resource[:relationships][name] ||= { data: [] }
66-
resource[:relationships][name][:data] += serializers.map { |serializer| { type: serializer.json_api_type, id: serializer.id.to_s } }
67-
end
68-
69-
def resource_identifier_id_for(serializer)
70-
if serializer.respond_to?(:id)
71-
serializer.id
72-
else
73-
serializer.object.id
74-
end
75-
end
76-
77-
def resource_identifier_for(serializer)
78-
type = resource_identifier_type_for(serializer)
79-
id = resource_identifier_id_for(serializer)
80-
81-
{ id: id.to_s, type: type }
82-
end
83-
84-
def resource_object_for(serializer, options = {})
85-
options[:fields] = @fieldset && @fieldset.fields_for(serializer)
86-
87-
cache_check(serializer) do
88-
result = resource_identifier_for(serializer)
89-
attributes = serializer.attributes(options).except(:id)
90-
result[:attributes] = attributes if attributes.any?
91-
result
92-
end
93-
end
94-
95-
def primary_data_for(serializer, options)
96-
if serializer.respond_to?(:each)
97-
serializer.map { |s| resource_object_for(s, options) }
98-
else
99-
resource_object_for(serializer, options)
100-
end
101-
end
102-
103-
def relationship_value_for(serializer, options = {})
104-
if serializer.respond_to?(:each)
105-
serializer.map { |s| resource_identifier_for(s) }
106-
else
107-
if options[:virtual_value]
108-
options[:virtual_value]
109-
elsif serializer && serializer.object
110-
resource_identifier_for(serializer)
111-
end
112-
end
113-
end
114-
115-
def relationships_for(serializer)
116-
Hash[serializer.associations.map { |association| [association.key, { data: relationship_value_for(association.serializer, association.options) }] }]
117-
end
118-
119-
def included_for(serializer)
120-
serializer.associations.flat_map { |assoc| _included_for(assoc.key, assoc.serializer) }.uniq
121-
end
122-
123-
def _included_for(resource_name, serializer, parent = nil)
124-
if serializer.respond_to?(:each)
125-
serializer.flat_map { |s| _included_for(resource_name, s, parent) }.uniq
126-
else
127-
return [] unless serializer && serializer.object
128-
result = []
129-
resource_path = [parent, resource_name].compact.join('.')
130-
131-
if include_assoc?(resource_path)
132-
primary_data = primary_data_for(serializer, @options)
133-
relationships = relationships_for(serializer)
134-
primary_data[:relationships] = relationships if relationships.any?
135-
result.push(primary_data)
136-
end
137-
138-
if include_nested_assoc?(resource_path)
139-
non_empty_associations = serializer.associations.select(&:serializer)
140-
141-
non_empty_associations.each do |association|
142-
result.concat(_included_for(association.key, association.serializer, resource_path))
143-
result.uniq!
144-
end
145-
end
146-
result
147-
end
148-
end
149-
150-
def include_assoc?(assoc)
151-
check_assoc("#{assoc}$")
152-
end
153-
154-
def include_nested_assoc?(assoc)
155-
check_assoc("#{assoc}.")
156-
end
157-
158-
def check_assoc(assoc)
159-
@options[:include].any? { |s| s.match(/^#{assoc.gsub('.', '\.')}/) }
160-
end
161-
162-
def add_links(options)
163-
links = @hash.fetch(:links) { {} }
164-
collection = serializer.object
165-
@hash[:links] = add_pagination_links(links, collection, options) if paginated?(collection)
166-
end
167-
168-
def add_pagination_links(links, resources, options)
169-
pagination_links = ActiveModel::Serializer::Adapter::JsonApi::PaginationLinks.new(resources, options[:context]).serializable_hash(options)
170-
links.update(pagination_links)
171-
end
172-
173-
def paginated?(collection)
174-
collection.respond_to?(:current_page) &&
175-
collection.respond_to?(:total_pages) &&
176-
collection.respond_to?(:size)
177-
end
2+
extend ActiveSupport::Autoload
3+
autoload :PaginationLinks
4+
autoload :FragmentCache
5+
6+
def initialize(serializer, options = {})
7+
super
8+
@hash = { data: [] }
9+
10+
@included = ActiveModel::Serializer::Utils.include_args_to_hash(@options[:include])
11+
fields = options.delete(:fields)
12+
if fields
13+
@fieldset = ActiveModel::Serializer::Fieldset.new(fields, serializer.json_key)
14+
else
15+
@fieldset = options[:fieldset]
16+
end
17+
end
18+
19+
def serializable_hash(options = nil)
20+
options ||= {}
21+
if serializer.respond_to?(:each)
22+
serializer.each do |s|
23+
result = self.class.new(s, @options.merge(fieldset: @fieldset)).serializable_hash(options)
24+
@hash[:data] << result[:data]
25+
26+
if result[:included]
27+
@hash[:included] ||= []
28+
@hash[:included] |= result[:included]
29+
end
30+
end
31+
32+
add_links(options)
33+
else
34+
primary_data = primary_data_for(serializer, options)
35+
relationships = relationships_for(serializer)
36+
included = included_for(serializer)
37+
@hash[:data] = primary_data
38+
@hash[:data][:relationships] = relationships if relationships.any?
39+
@hash[:included] = included if included.any?
40+
end
41+
@hash
42+
end
43+
44+
def fragment_cache(cached_hash, non_cached_hash)
45+
root = false if @options.include?(:include)
46+
ActiveModel::Serializer::Adapter::JsonApi::FragmentCache.new().fragment_cache(root, cached_hash, non_cached_hash)
47+
end
48+
49+
private
50+
51+
def resource_identifier_type_for(serializer)
52+
if ActiveModel::Serializer.config.jsonapi_resource_type == :singular
53+
serializer.object.class.model_name.singular
54+
else
55+
serializer.object.class.model_name.plural
56+
end
57+
end
58+
59+
def add_relationships(resource, name, serializers)
60+
resource[:relationships] ||= {}
61+
resource[:relationships][name] ||= { data: [] }
62+
resource[:relationships][name][:data] += serializers.map { |serializer| { type: serializer.json_api_type, id: serializer.id.to_s } }
63+
end
64+
65+
def resource_identifier_id_for(serializer)
66+
if serializer.respond_to?(:id)
67+
serializer.id
68+
else
69+
serializer.object.id
70+
end
71+
end
72+
73+
def resource_identifier_for(serializer)
74+
type = resource_identifier_type_for(serializer)
75+
id = resource_identifier_id_for(serializer)
76+
77+
{ id: id.to_s, type: type }
78+
end
79+
80+
def resource_object_for(serializer, options = {})
81+
options[:fields] = @fieldset && @fieldset.fields_for(serializer)
82+
83+
cache_check(serializer) do
84+
result = resource_identifier_for(serializer)
85+
attributes = serializer.attributes(options).except(:id)
86+
result[:attributes] = attributes if attributes.any?
87+
result
88+
end
89+
end
90+
91+
def primary_data_for(serializer, options)
92+
if serializer.respond_to?(:each)
93+
serializer.map { |s| resource_object_for(s, options) }
94+
else
95+
resource_object_for(serializer, options)
96+
end
97+
end
98+
99+
def relationship_value_for(serializer, options = {})
100+
if serializer.respond_to?(:each)
101+
serializer.map { |s| resource_identifier_for(s) }
102+
else
103+
if options[:virtual_value]
104+
options[:virtual_value]
105+
elsif serializer && serializer.object
106+
resource_identifier_for(serializer)
107+
end
108+
end
109+
end
110+
111+
def relationships_for(serializer)
112+
Hash[serializer.associations.map { |association| [association.key, { data: relationship_value_for(association.serializer, association.options) }] }]
113+
end
114+
115+
def included_for(serializer)
116+
included = @included.flat_map do |inc|
117+
association = serializer.associations.find { |assoc| assoc.key == inc.first }
118+
_included_for(association.serializer, inc.second) if association
119+
end
120+
121+
included.uniq
122+
end
123+
124+
def _included_for(serializer, includes)
125+
if serializer.respond_to?(:each)
126+
serializer.flat_map { |s| _included_for(s, includes) }.uniq
127+
else
128+
return [] unless serializer && serializer.object
129+
130+
primary_data = primary_data_for(serializer, @options)
131+
relationships = relationships_for(serializer)
132+
primary_data[:relationships] = relationships if relationships.any?
133+
134+
included = [primary_data]
135+
136+
includes.each do |inc|
137+
association = serializer.associations.find { |assoc| assoc.key == inc.first }
138+
if association
139+
included.concat(_included_for(association.serializer, inc.second))
140+
included.uniq!
141+
end
142+
end
143+
144+
included
145+
end
146+
end
147+
148+
def add_links(options)
149+
links = @hash.fetch(:links) { {} }
150+
collection = serializer.object
151+
@hash[:links] = add_pagination_links(links, collection, options) if paginated?(collection)
152+
end
153+
154+
def add_pagination_links(links, resources, options)
155+
pagination_links = ActiveModel::Serializer::Adapter::JsonApi::PaginationLinks.new(resources, options[:context]).serializable_hash(options)
156+
links.update(pagination_links)
157+
end
158+
159+
def paginated?(collection)
160+
collection.respond_to?(:current_page) &&
161+
collection.respond_to?(:total_pages) &&
162+
collection.respond_to?(:size)
163+
end
178164
end
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
module ActiveModel::Serializer::Utils
2+
module_function
3+
4+
def include_string_to_hash(included)
5+
included.delete(' ').split(',').inject({}) do |hash, path|
6+
hash.deep_merge!(path.split('.').reverse_each.inject({}) { |a, e| { e.to_sym => a } })
7+
end
8+
end
9+
10+
def include_args_to_hash(included)
11+
case included
12+
when Symbol
13+
{ included => {} }
14+
when Hash
15+
Hash[included.map { |key, value| [key, include_args_to_hash(value)] }]
16+
when Array
17+
included.inject({}) { |a, e| a.merge!(include_args_to_hash(e)) }
18+
when String
19+
include_string_to_hash(included)
20+
else
21+
{}
22+
end
23+
end
24+
end

0 commit comments

Comments
 (0)