Skip to content

Commit cf55dc8

Browse files
committed
resurrect some old code that sideloads, and should have infinite recursion protection
1 parent 0c18d22 commit cf55dc8

File tree

1 file changed

+199
-100
lines changed

1 file changed

+199
-100
lines changed
Lines changed: 199 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,209 @@
11
module ActiveModel
22
class Serializer
33
module Adapter
4-
class FlatJson < Attributes
5-
attr_accessor :_flattened
6-
7-
def serializable_hash(*)
8-
hash = { root => super }
9-
self._flattened = {}
10-
flatten(hash)
11-
singularize_lone_objects
12-
13-
_flattened
14-
end
15-
16-
# recursively iterates over an already adapted hash and
17-
# unwraps nested objects, adding that to the root of the
18-
# the hash
19-
#
20-
# @param [Hash] adapted_hash the hash to unwrap
21-
def flatten(adapted_hash)
22-
adapted_hash.each_with_object({}) do |(key, item), hash|
23-
if item.is_a?(Array)
24-
is_of_hashes = item.first.is_a?(Hash)
25-
new_key = key.to_s.include?('_id') ? key : ids_name_for(key)
26-
if is_of_hashes
27-
hash[new_key] = item.map do |i|
28-
add(key, flatten(i))
29-
i[:id]
4+
class FlatJson < Base
5+
6+
# the list of what has already been serialized will be kept here to
7+
# help avoid infinite recursion
8+
#
9+
# this should be a list of association_name to a list of objects
10+
attr_accessor :serialized
11+
12+
# When we are sideloading associations, we can more easily track what
13+
# has been serialized, so that we avoid infinite
14+
# recursion / serialization.
15+
def initialize(*args)
16+
@serialized = {}
17+
18+
super(*args)
19+
end
20+
21+
def serializable_hash(options = nil)
22+
options ||= {}
23+
24+
# begin recursive serialization
25+
# TODO: this will evaluate ALL specified relationships
26+
# throughout the entire tree.
27+
#
28+
# Do we want a way to limit this?
29+
serialize_hash(options)
30+
31+
singularize_lone_objects
32+
33+
@serialized
34+
end
35+
36+
37+
def serialize_hash(options)
38+
result = {}
39+
40+
if serializer.respond_to?(:each)
41+
# TODO: Is this ever hit?
42+
result = serialize_array(serializer, options)
43+
else
44+
# skip if we are already serialized
45+
key_name = serializer.object.class.name.tableize
46+
existing_of_kind = @serialized[key_name]
47+
exists = existing_of_kind ? existing_of_kind.select{|a| a[:id] == s.object.id } : false
48+
return if exists
49+
50+
# we aren't an array! woo!
51+
result = serialized_attributes_of(serializer, options)
52+
if result.nil?
53+
puts serializer
54+
puts options
55+
end
56+
# now, go over our associations, and add them to the master
57+
# serialized hash
58+
serializer.associations.each do |association|
59+
serializer = association.serializer
60+
opts = association.options
61+
62+
# make sure the association key exists in the master
63+
# serialized hash
64+
@serialized[association.key] ||= []
65+
66+
if serializer.respond_to?(:each)
67+
array = serialize_array(serializer, opts)
68+
association_list = @serialized[association.key]
69+
70+
# ensure that we don't add duplicates
71+
array.each do |item|
72+
if not association_list.include?(item)
73+
association_list << item
74+
end
75+
end
76+
77+
# re-set the list for this model
78+
@serialized[association.key] = association_list
79+
80+
# add the ids to the result
81+
result[ids_name_for(association.key)] = array.map{|a| a[:id] }
82+
else
83+
hash = serialized_or_virtual_object(serializer, options)
84+
add(association.key, hash)
85+
86+
# add the id to the result
87+
result[id_name_for(association.key)] = hash[:id]
88+
end
89+
90+
end
91+
end
92+
93+
# add to the list of the serialized
94+
@serialized[key_name] ||= []
95+
add(key_name, result)
96+
97+
result
98+
end
99+
100+
def serialized_or_virtual_object(serializer, options)
101+
return options[:virtual_value] if options[:virtual_value]
102+
103+
if serializer && serializer.object
104+
serialized_attributes_of(serializer, options)
105+
end
106+
end
107+
108+
def add(key, data)
109+
unless associations_contain?(data, key)
110+
if @serialized[key].is_a?(Hash)
111+
# make array
112+
value = @serialized[key]
113+
@serialized[key] = [value, data]
114+
else
115+
# already is array
116+
@serialized[key] << data
117+
end
118+
end
119+
end
120+
121+
def serialize_array(serializer, options)
122+
array = serializer.map { |s|
123+
js = FlatJson.new(s)
124+
serialized = js.serialize_hash(options)
125+
126+
# keep the associations up to date
127+
append_to_serialized(js.serialized)
128+
129+
serialized
130+
}
131+
132+
# remove nils
133+
array.compact
134+
end
135+
136+
def ids_name_for(name)
137+
id_name_for(name).to_s.pluralize.to_sym
138+
end
139+
140+
def id_name_for(name)
141+
name.to_s.singularize.foreign_key.to_sym
142+
end
143+
144+
145+
def serialized_attributes_of(item, options)
146+
cache_check(item) do
147+
item.attributes(options)
30148
end
31-
else
32-
hash[new_key] = item
33149
end
34-
elsif item.is_a?(Hash)
35-
new_key = id_name_for(key)
36-
add(key, flatten(item))
37-
hash[new_key] = item[:id]
38-
else
39-
hash[key] = item
40-
end
41-
end
42-
end
43-
44-
# @note _flatten[key] will always be an array
45-
#
46-
# @param [Symbol] key the type of data
47-
# @param [Object] data model must have id field
48-
def add(key, data)
49-
return if include?(data, key)
50-
# make array - if there ends up only being one of this kind
51-
# object, it will be un-arrayed later in singularize_lone_objects
52-
# if this array only contains one object
53-
_flattened[key] ||= []
54-
# make sure that we aren't adding the same object with different data
55-
old = _flattened[key].find { |i| i[:id] == data[:id] }
56-
# if data is the 'same' as old, merge
57-
# - new object will have additional associations
58-
# (list of ids for each relationship)
59-
if old
60-
_flattened[key].delete(old)
61-
data = old.merge(data)
62-
end
63-
64-
_flattened[key] << data
65-
end
66-
67-
# converts :association_name to :association_name_ids
68-
def ids_name_for(name)
69-
id_name_for(name).to_s.pluralize.to_sym
70-
end
71-
72-
# converts :association_name to :association_name_id
73-
def id_name_for(name)
74-
name.to_s.singularize.foreign_key.to_sym
75-
end
76-
77-
# To make keeping track of serialized objects easier,
78-
# they are all tracked in arrays with plural keys.
79-
#
80-
# Once the recursion is done, we don't need plural keys / arrays
81-
# for singular objects.
82-
#
83-
# This method converts:
84-
# objects: [{data}]
85-
# # to
86-
# object: {data}
87-
#
88-
# This modifies and returns @serialized
89-
def singularize_lone_objects
90-
temp = {}
91-
92-
_flattened.each do |key, data|
93-
if data.length > 1
94-
temp[key.to_s.pluralize.to_sym] = data
95-
else
96-
temp[key.to_s.singularize.to_sym] = data.first
97-
end
98-
end
99150

100-
self._flattened = temp
101-
end
151+
# To make keeping track of serialized objects easier,
152+
# they are all tracked in arrays with plural keys.
153+
#
154+
# Once the recursion is done, we don't need plural keys / arrays
155+
# for singular objects.
156+
#
157+
# This method converts:
158+
# objects: [{data}]
159+
# # to
160+
# object: {data}
161+
#
162+
# This modifies and returns @serialized
163+
def singularize_lone_objects
164+
temp = {}
102165

103-
def include?(item, key)
104-
return false if _flattened[key].nil?
105-
_flattened[key] == item || _flattened[key].include?(item)
106-
end
107-
end
166+
@serialized.each do |key, data|
167+
if data.length > 1
168+
temp[key.to_s.pluralize.to_sym] = data
169+
else
170+
temp[key.to_s.singularize.to_sym] = data.first
171+
end
172+
end
173+
174+
@serialized = temp
175+
end
176+
177+
# adds a set of objects to the @serialized structure,
178+
# while checking to make sure that a particular object
179+
# isn't already tracked.
180+
def append_to_serialized(serialized_objects)
181+
serialized_objects ||= {}
182+
183+
serialized_objects.each do |association_name, data|
184+
@serialized[association_name] ||= []
185+
186+
if data.is_a?(Array)
187+
data.each do |sub_data|
188+
append_to_serialized(association_name => sub_data)
189+
end
190+
else
191+
unless associations_contain?(data, association_name)
192+
add(association_name, data)
193+
end
194+
end
195+
end
196+
197+
@serialized
198+
end
199+
200+
def associations_contain?(item, key)
201+
return false if @serialized[key].nil?
202+
203+
@serialized[key] == item || @serialized[key].include?(item)
204+
end
205+
206+
end
108207
end
109208
end
110209
end

0 commit comments

Comments
 (0)