vorpal 0.0.5.1 → 0.0.6.rc1
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 +8 -8
- data/lib/vorpal/aggregate_repository.rb +152 -236
- data/lib/vorpal/aggregate_traversal.rb +50 -0
- data/lib/vorpal/aggregate_utils.rb +37 -0
- data/lib/vorpal/config_builder.rb +2 -2
- data/lib/vorpal/configs.rb +73 -74
- data/lib/vorpal/db_driver.rb +50 -0
- data/lib/vorpal/db_loader.rb +131 -0
- data/lib/vorpal/identity_map.rb +4 -0
- data/lib/vorpal/loaded_objects.rb +53 -0
- data/lib/vorpal/util/array_hash.rb +13 -0
- data/lib/vorpal/{hash_initialization.rb → util/hash_initialization.rb} +0 -0
- data/lib/vorpal/version.rb +1 -1
- data/spec/integration_spec_helper.rb +3 -1
- data/spec/vorpal/aggregate_repository_spec.rb +60 -7
- data/vorpal.gemspec +3 -1
- metadata +41 -8
- data/lib/vorpal/traversal.rb +0 -98
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NzRmMjgwNmFkMjU4NGExMDQ1M2E2NWEyMjMxZGJjODJkMDVkNTEzNg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
NzY4Nzg1ZmM5MDBmN2EwOGM5ZDdlYTEzNTJmMTIxNWEwMDdmMDc0Mg==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
YTAzZmQ1M2IxOWU5NTI5MmYyMGJhNzJkZjAyZTkzOGJjNmIyODk1NmU4MGM0
|
10
|
+
YTc1ZWNiNGZlODdmMjg0MTkwNjBhNjBjMzU5ZmJjM2NiOWY5MGY5NTRiMWNl
|
11
|
+
ZWE5ZGEzMGU1YzBhMDNmNjljYTEwNzMwMDlkODQ2M2ZlNzU2YmU=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
Yjg4NWUyYmE5YWQ2MjVlMTExZTllZDA1MWU1OTJhYWY0MmRlZmMzMjY1ZGEy
|
14
|
+
ZGVmYzk3ZTE1MjhkMjVlYjNjOGRmNzExYWM5Zjc1MjlhY2E2ZDU5MDQ3Nzk3
|
15
|
+
Y2JmNjg4NGYzYjMwYzU5ZGRlMjYzNmMwYzc0NjM3YjlkYTMyNGI=
|
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'vorpal/identity_map'
|
2
|
-
require 'vorpal/
|
2
|
+
require 'vorpal/aggregate_utils'
|
3
|
+
require 'vorpal/db_loader'
|
4
|
+
require 'vorpal/db_driver'
|
3
5
|
|
4
6
|
module Vorpal
|
5
7
|
class AggregateRepository
|
@@ -16,29 +18,37 @@ class AggregateRepository
|
|
16
18
|
# be inserted, updated, or deleted. However, the relationships to these
|
17
19
|
# objects (provided they are stored within the aggregate) will be saved.
|
18
20
|
#
|
19
|
-
# @param
|
21
|
+
# @param root [Object] Root of the aggregate to be saved.
|
20
22
|
# @return [Object] Root of the aggregate.
|
21
|
-
def persist(
|
22
|
-
|
23
|
-
serialize(object, mapping)
|
24
|
-
new_objects = get_unsaved_objects(mapping.keys)
|
25
|
-
set_primary_keys(object, mapping)
|
26
|
-
set_foreign_keys(object, mapping)
|
27
|
-
remove_orphans(object, mapping)
|
28
|
-
save(object, mapping)
|
29
|
-
object
|
30
|
-
rescue
|
31
|
-
nil_out_object_ids(new_objects)
|
32
|
-
raise
|
23
|
+
def persist(root)
|
24
|
+
persist_all(Array(root)).first
|
33
25
|
end
|
34
26
|
|
35
|
-
# Like {#persist} but operates on multiple aggregates. Roots
|
27
|
+
# Like {#persist} but operates on multiple aggregates. Roots must
|
36
28
|
# be of the same type.
|
37
29
|
#
|
38
|
-
# @param
|
30
|
+
# @param roots [[Object]] array of aggregate roots to be saved.
|
39
31
|
# @return [[Object]] array of aggregate roots.
|
40
|
-
def persist_all(
|
41
|
-
|
32
|
+
def persist_all(roots)
|
33
|
+
return roots if roots.empty?
|
34
|
+
|
35
|
+
all_owned_objects = all_owned_objects(roots)
|
36
|
+
mapping = {}
|
37
|
+
loaded_db_objects = load_owned_from_db(roots.map(&:id), roots.first.class)
|
38
|
+
|
39
|
+
serialize(all_owned_objects, mapping, loaded_db_objects)
|
40
|
+
new_objects = get_unsaved_objects(mapping.keys)
|
41
|
+
begin
|
42
|
+
set_primary_keys(all_owned_objects, mapping)
|
43
|
+
set_foreign_keys(all_owned_objects, mapping)
|
44
|
+
remove_orphans(mapping, loaded_db_objects)
|
45
|
+
save(all_owned_objects, new_objects, mapping)
|
46
|
+
|
47
|
+
return roots
|
48
|
+
rescue
|
49
|
+
nil_out_object_ids(new_objects)
|
50
|
+
raise
|
51
|
+
end
|
42
52
|
end
|
43
53
|
|
44
54
|
# Loads an aggregate from the DB. Will eagerly load all objects in the
|
@@ -66,117 +76,115 @@ class AggregateRepository
|
|
66
76
|
# operation.
|
67
77
|
# @return [[Object]] Entities with the given primary key values and type.
|
68
78
|
def load_all(ids, domain_class, identity_map=IdentityMap.new)
|
69
|
-
ids.
|
70
|
-
|
71
|
-
|
72
|
-
|
79
|
+
db_objects = load_from_db(ids, domain_class).all_objects
|
80
|
+
deserialize(db_objects, identity_map)
|
81
|
+
set_associations(db_objects, identity_map)
|
82
|
+
|
83
|
+
identity_map.map_raw(ids, @configs.config_for(domain_class).db_class)
|
73
84
|
end
|
74
85
|
|
75
86
|
# Removes an aggregate from the DB. Even if the aggregate contains unsaved
|
76
87
|
# changes this method will correctly remove everything.
|
77
88
|
#
|
78
|
-
# @param
|
89
|
+
# @param root [Object] Root of the aggregate to be destroyed.
|
79
90
|
# @return [Object] Root that was passed in.
|
80
|
-
def destroy(
|
81
|
-
|
82
|
-
db_object = config.find_in_db(object)
|
83
|
-
@traversal.accept_for_db(db_object, DestroyVisitor.new())
|
84
|
-
object
|
91
|
+
def destroy(root)
|
92
|
+
destroy_all(Array(root)).first
|
85
93
|
end
|
86
94
|
|
87
|
-
# Like {#destroy} but operates on multiple aggregates. Roots
|
95
|
+
# Like {#destroy} but operates on multiple aggregates. Roots must
|
88
96
|
# be of the same type.
|
89
97
|
#
|
90
|
-
# @param
|
98
|
+
# @param roots [[Object]] Array of roots of the aggregates to be destroyed.
|
91
99
|
# @return [[Object]] Roots that were passed in.
|
92
|
-
def destroy_all(
|
93
|
-
|
100
|
+
def destroy_all(roots)
|
101
|
+
return roots if roots.empty?
|
102
|
+
loaded_db_objects = load_owned_from_db(roots.map(&:id), roots.first.class)
|
103
|
+
loaded_db_objects.each do |config, db_objects|
|
104
|
+
DbDriver.destroy(config, db_objects)
|
105
|
+
end
|
106
|
+
roots
|
94
107
|
end
|
95
108
|
|
96
109
|
private
|
97
110
|
|
98
|
-
def
|
99
|
-
@configs
|
100
|
-
@traversal = Traversal.new(@configs)
|
111
|
+
def all_owned_objects(roots)
|
112
|
+
AggregateUtils.group_by_type(roots, @configs)
|
101
113
|
end
|
102
114
|
|
103
|
-
def
|
104
|
-
|
105
|
-
set_associations(db_object, identity_map)
|
106
|
-
identity_map.get(db_object)
|
115
|
+
def load_from_db(ids, domain_class, only_owned=false)
|
116
|
+
DbLoader.new(@configs, only_owned).load_from_db(ids, domain_class)
|
107
117
|
end
|
108
118
|
|
109
|
-
def
|
110
|
-
|
111
|
-
end
|
112
|
-
|
113
|
-
def set_primary_keys(object, mapping)
|
114
|
-
@traversal.accept_for_domain(object, IdentityVisitor.new(mapping))
|
115
|
-
mapping.rehash # needs to happen because setting the id on an AR::Base model changes its hash value
|
119
|
+
def load_owned_from_db(ids, domain_class)
|
120
|
+
load_from_db(ids, domain_class, true)
|
116
121
|
end
|
117
122
|
|
118
|
-
def
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
@traversal.accept_for_domain(object, SaveVisitor.new(mapping))
|
124
|
-
end
|
125
|
-
|
126
|
-
def remove_orphans(object, mapping)
|
127
|
-
diff_visitor = AggregateDiffVisitor.new(mapping.values)
|
128
|
-
@traversal.accept_for_db(mapping[object], diff_visitor)
|
129
|
-
|
130
|
-
orphans = diff_visitor.orphans
|
131
|
-
orphans.each { |o| @configs.config_for_db(o.class).destroy(o) }
|
123
|
+
def deserialize(db_objects, identity_map)
|
124
|
+
db_objects.each do |db_object|
|
125
|
+
# TODO: There is probably a bug here when you have something in the IdentityMap that is stale.
|
126
|
+
identity_map.get_and_set(db_object) { @configs.config_for_db(db_object.class).deserialize(db_object) }
|
127
|
+
end
|
132
128
|
end
|
133
129
|
|
134
|
-
def
|
135
|
-
|
136
|
-
|
130
|
+
def set_associations(db_objects, identity_map)
|
131
|
+
db_objects.each do |db_object|
|
132
|
+
config = @configs.config_for_db(db_object.class)
|
133
|
+
config.has_manys.each do |has_many_config|
|
134
|
+
db_children = find_associated(db_object, has_many_config, db_objects)
|
135
|
+
associate_one_to_many(db_object, db_children, has_many_config, identity_map)
|
136
|
+
end
|
137
137
|
|
138
|
-
|
139
|
-
|
140
|
-
|
138
|
+
config.has_ones.each do |has_one_config|
|
139
|
+
db_children = find_associated(db_object, has_one_config, db_objects)
|
140
|
+
associate_one_to_one(db_object, db_children.first, has_one_config, identity_map)
|
141
|
+
end
|
141
142
|
|
142
|
-
|
143
|
-
|
143
|
+
config.belongs_tos.each do |belongs_to_config|
|
144
|
+
db_children = find_associated(db_object, belongs_to_config, db_objects)
|
145
|
+
associate_one_to_one(db_object, db_children.first, belongs_to_config, identity_map)
|
146
|
+
end
|
147
|
+
end
|
144
148
|
end
|
145
149
|
|
146
|
-
def
|
147
|
-
|
148
|
-
|
150
|
+
def find_associated(db_object, association_config, db_objects)
|
151
|
+
db_objects.find_all do |db_child|
|
152
|
+
association_config.associated?(db_object, db_child)
|
153
|
+
end
|
149
154
|
end
|
150
|
-
end
|
151
|
-
|
152
|
-
# @private
|
153
|
-
class SerializeVisitor
|
154
|
-
include AggregateVisitorTemplate
|
155
155
|
|
156
|
-
def
|
157
|
-
|
156
|
+
def associate_one_to_many(db_object, db_children, one_to_many, identity_map)
|
157
|
+
parent = identity_map.get(db_object)
|
158
|
+
children = identity_map.map(db_children)
|
159
|
+
one_to_many.set_children(parent, children)
|
158
160
|
end
|
159
161
|
|
160
|
-
def
|
161
|
-
|
162
|
+
def associate_one_to_one(db_parent, db_child, one_to_one_config, identity_map)
|
163
|
+
parent = identity_map.get(db_parent)
|
164
|
+
child = identity_map.get(db_child)
|
165
|
+
one_to_one_config.set_child(parent, child)
|
162
166
|
end
|
163
167
|
|
164
|
-
def
|
165
|
-
|
168
|
+
def configure(class_configs)
|
169
|
+
@configs = MasterConfig.new(class_configs)
|
166
170
|
end
|
167
171
|
|
168
|
-
def serialize(
|
169
|
-
|
170
|
-
|
172
|
+
def serialize(owned_objects, mapping, loaded_db_objects)
|
173
|
+
owned_objects.each do |config, objects|
|
174
|
+
objects.each do |object|
|
175
|
+
db_object = serialize_object(object, config, loaded_db_objects)
|
176
|
+
mapping[object] = db_object
|
177
|
+
end
|
178
|
+
end
|
171
179
|
end
|
172
180
|
|
173
|
-
def serialize_object(object, config)
|
181
|
+
def serialize_object(object, config, loaded_db_objects)
|
174
182
|
if config.serialization_required?
|
175
183
|
attributes = config.serialize(object)
|
176
184
|
if object.id.nil?
|
177
185
|
config.build_db_object(attributes)
|
178
186
|
else
|
179
|
-
db_object =
|
187
|
+
db_object = loaded_db_objects.find_by_id(object, config)
|
180
188
|
config.set_db_object_attributes(db_object, attributes)
|
181
189
|
db_object
|
182
190
|
end
|
@@ -184,168 +192,76 @@ class SerializeVisitor
|
|
184
192
|
object
|
185
193
|
end
|
186
194
|
end
|
187
|
-
end
|
188
|
-
|
189
|
-
# @private
|
190
|
-
class IdentityVisitor
|
191
|
-
include AggregateVisitorTemplate
|
192
|
-
|
193
|
-
def initialize(mapping)
|
194
|
-
@mapping = mapping
|
195
|
-
end
|
196
|
-
|
197
|
-
def visit_object(object, config)
|
198
|
-
set_primary_key(object, config)
|
199
|
-
end
|
200
|
-
|
201
|
-
def continue_traversal?(association_config)
|
202
|
-
association_config.owned
|
203
|
-
end
|
204
|
-
|
205
|
-
private
|
206
|
-
|
207
|
-
def set_primary_key(object, config)
|
208
|
-
return unless object.id.nil?
|
209
|
-
|
210
|
-
primary_key = config.get_primary_keys(1).first
|
211
|
-
|
212
|
-
@mapping[object].id = primary_key
|
213
|
-
object.id = primary_key
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
# @private
|
218
|
-
class PersistenceAssociationVisitor
|
219
|
-
include AggregateVisitorTemplate
|
220
|
-
|
221
|
-
def initialize(mapping)
|
222
|
-
@mapping = mapping
|
223
|
-
end
|
224
195
|
|
225
|
-
def
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
def visit_has_many(parent, children, has_many_config)
|
235
|
-
return unless has_many_config.owned
|
236
|
-
children.each do |child|
|
237
|
-
has_many_config.set_foreign_key(@mapping[child], parent)
|
196
|
+
def set_primary_keys(owned_objects, mapping)
|
197
|
+
owned_objects.each do |config, objects|
|
198
|
+
in_need_of_primary_keys = objects.find_all { |obj| obj.id.nil? }
|
199
|
+
primary_keys = DbDriver.get_primary_keys(config, in_need_of_primary_keys.length)
|
200
|
+
in_need_of_primary_keys.zip(primary_keys).each do |object, primary_key|
|
201
|
+
mapping[object].id = primary_key
|
202
|
+
object.id = primary_key
|
203
|
+
end
|
238
204
|
end
|
205
|
+
mapping.rehash # needs to happen because setting the id on an AR::Base model changes its hash value
|
239
206
|
end
|
240
207
|
|
241
|
-
def
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
def initialize(db_objects_in_aggregate)
|
268
|
-
@db_objects_in_aggregate = db_objects_in_aggregate
|
269
|
-
@db_objects_in_db = []
|
270
|
-
end
|
271
|
-
|
272
|
-
def visit_object(db_object, config)
|
273
|
-
@db_objects_in_db << db_object
|
274
|
-
end
|
275
|
-
|
276
|
-
def continue_traversal?(association_config)
|
277
|
-
association_config.owned
|
278
|
-
end
|
279
|
-
|
280
|
-
def orphans
|
281
|
-
@db_objects_in_db - @db_objects_in_aggregate
|
282
|
-
end
|
283
|
-
end
|
284
|
-
|
285
|
-
# @private
|
286
|
-
class DeserializeVisitor
|
287
|
-
include AggregateVisitorTemplate
|
288
|
-
|
289
|
-
def initialize(identity_map)
|
290
|
-
@identity_map = identity_map
|
291
|
-
end
|
292
|
-
|
293
|
-
def visit_object(db_object, config)
|
294
|
-
deserialize_db_object(db_object, config)
|
295
|
-
end
|
296
|
-
|
297
|
-
private
|
298
|
-
|
299
|
-
def deserialize_db_object(db_object, config)
|
300
|
-
@identity_map.get_and_set(db_object) { config.deserialize(db_object) }
|
301
|
-
end
|
302
|
-
end
|
303
|
-
|
304
|
-
# @private
|
305
|
-
class DomainAssociationVisitor
|
306
|
-
include AggregateVisitorTemplate
|
307
|
-
|
308
|
-
def initialize(identity_map)
|
309
|
-
@identity_map = identity_map
|
310
|
-
end
|
311
|
-
|
312
|
-
def visit_belongs_to(db_object, db_child, belongs_to_config)
|
313
|
-
associate_one_to_one(db_object, db_child, belongs_to_config)
|
314
|
-
end
|
315
|
-
|
316
|
-
def visit_has_one(db_parent, db_child, has_one_config)
|
317
|
-
associate_one_to_one(db_parent, db_child, has_one_config)
|
318
|
-
end
|
319
|
-
|
320
|
-
def visit_has_many(db_object, db_children, has_many_config)
|
321
|
-
associate_one_to_many(db_object, db_children, has_many_config)
|
208
|
+
def set_foreign_keys(owned_objects, mapping)
|
209
|
+
owned_objects.each do |config, objects|
|
210
|
+
objects.each do |object|
|
211
|
+
config.has_manys.each do |has_many_config|
|
212
|
+
if has_many_config.owned
|
213
|
+
children = has_many_config.get_children(object)
|
214
|
+
children.each do |child|
|
215
|
+
has_many_config.set_foreign_key(mapping[child], object)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
config.has_ones.each do |has_one_config|
|
221
|
+
if has_one_config.owned
|
222
|
+
child = has_one_config.get_child(object)
|
223
|
+
has_one_config.set_foreign_key(mapping[child], object)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
config.belongs_tos.each do |belongs_to_config|
|
228
|
+
child = belongs_to_config.get_child(object)
|
229
|
+
belongs_to_config.set_foreign_key(mapping[object], child)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
322
233
|
end
|
323
234
|
|
324
|
-
|
235
|
+
def save(owned_objects, new_objects, mapping)
|
236
|
+
grouped_new_objects = new_objects.group_by { |obj| @configs.config_for(obj.class) }
|
237
|
+
owned_objects.each do |config, objects|
|
238
|
+
objects_to_insert = grouped_new_objects[config] || []
|
239
|
+
db_objects_to_insert = objects_to_insert.map { |obj| mapping[obj] }
|
240
|
+
DbDriver.insert(config, db_objects_to_insert)
|
325
241
|
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
242
|
+
objects_to_update = objects - objects_to_insert
|
243
|
+
db_objects_to_update = objects_to_update.map { |obj| mapping[obj] }
|
244
|
+
DbDriver.update(config, db_objects_to_update)
|
245
|
+
end
|
330
246
|
end
|
331
247
|
|
332
|
-
def
|
333
|
-
|
334
|
-
|
335
|
-
|
248
|
+
def remove_orphans(mapping, loaded_db_objects)
|
249
|
+
db_objects_in_aggregate = mapping.values
|
250
|
+
db_objects_in_db = loaded_db_objects.all_objects
|
251
|
+
all_orphans = db_objects_in_db - db_objects_in_aggregate
|
252
|
+
grouped_orphans = all_orphans.group_by { |o| @configs.config_for_db(o.class) }
|
253
|
+
grouped_orphans.each do |config, orphans|
|
254
|
+
DbDriver.destroy(config, orphans)
|
255
|
+
end
|
336
256
|
end
|
337
|
-
end
|
338
|
-
|
339
|
-
# @private
|
340
|
-
class DestroyVisitor
|
341
|
-
include AggregateVisitorTemplate
|
342
257
|
|
343
|
-
def
|
344
|
-
|
258
|
+
def get_unsaved_objects(objects)
|
259
|
+
objects.find_all { |object| object.id.nil? }
|
345
260
|
end
|
346
261
|
|
347
|
-
def
|
348
|
-
|
262
|
+
def nil_out_object_ids(objects)
|
263
|
+
objects ||= []
|
264
|
+
objects.each { |object| object.id = nil }
|
349
265
|
end
|
350
266
|
end
|
351
267
|
|