vorpal 0.0.5.1 → 0.0.6.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MGJmZjcxZDY2NzEzMmYyNzFjNjg2NTEzODcxMTY0NjcyNzQzMGE4Nw==
4
+ NzRmMjgwNmFkMjU4NGExMDQ1M2E2NWEyMjMxZGJjODJkMDVkNTEzNg==
5
5
  data.tar.gz: !binary |-
6
- YWFhYzNmNjE5NjFjMTg0NjZiNmQ2NjExMDRlOWRkZTdiYWM2MGVjMg==
6
+ NzY4Nzg1ZmM5MDBmN2EwOGM5ZDdlYTEzNTJmMTIxNWEwMDdmMDc0Mg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- YmNhNzJlMzU0ZTA5M2VjOTBhMTA2NjJjOWYzNjc0OGZhNWM5ZTczYzgzNjJh
10
- ZDAzNWJkMDE5ZjA3ZjcyODZhMWY2MzNlMGM2ZWMyM2M4NDZkZGE3MGI1ODNh
11
- MWRjMjNlZTc5NDJlYmY4NmE0NDcyOGY5NTVkMGMxMGEyN2FmNTg=
9
+ YTAzZmQ1M2IxOWU5NTI5MmYyMGJhNzJkZjAyZTkzOGJjNmIyODk1NmU4MGM0
10
+ YTc1ZWNiNGZlODdmMjg0MTkwNjBhNjBjMzU5ZmJjM2NiOWY5MGY5NTRiMWNl
11
+ ZWE5ZGEzMGU1YzBhMDNmNjljYTEwNzMwMDlkODQ2M2ZlNzU2YmU=
12
12
  data.tar.gz: !binary |-
13
- YzQxOWNmZDM2ZDYzNDE0ODFmZTUxN2VlNzg3MDU3M2FhMzU5OGY1NTdlNmYx
14
- Yjg0ZTkxMWY0Y2E3NmNkMmJhMTYxNjU0NzM5Y2Y5NTVkYzI0ZDI0ODdlNWM5
15
- NzU2ZmZhMjBkODY3MGFiMGUzOThmZmNkYTUxNjEwOWJmNWQ5ZTk=
13
+ Yjg4NWUyYmE5YWQ2MjVlMTExZTllZDA1MWU1OTJhYWY0MmRlZmMzMjY1ZGEy
14
+ ZGVmYzk3ZTE1MjhkMjVlYjNjOGRmNzExYWM5Zjc1MjlhY2E2ZDU5MDQ3Nzk3
15
+ Y2JmNjg4NGYzYjMwYzU5ZGRlMjYzNmMwYzc0NjM3YjlkYTMyNGI=
@@ -1,5 +1,7 @@
1
1
  require 'vorpal/identity_map'
2
- require 'vorpal/traversal'
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 object [Object] Root of the aggregate to be saved.
21
+ # @param root [Object] Root of the aggregate to be saved.
20
22
  # @return [Object] Root of the aggregate.
21
- def persist(object)
22
- mapping = {}
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 do not need to
27
+ # Like {#persist} but operates on multiple aggregates. Roots must
36
28
  # be of the same type.
37
29
  #
38
- # @param objects [[Object]] array of aggregate roots to be saved.
30
+ # @param roots [[Object]] array of aggregate roots to be saved.
39
31
  # @return [[Object]] array of aggregate roots.
40
- def persist_all(objects)
41
- objects.map(&method(:persist))
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.map do |id|
70
- db_object = @configs.config_for(domain_class).load_by_id(id)
71
- hydrate(db_object, identity_map)
72
- end
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 object [Object] Root of the aggregate to be destroyed.
89
+ # @param root [Object] Root of the aggregate to be destroyed.
79
90
  # @return [Object] Root that was passed in.
80
- def destroy(object)
81
- config = @configs.config_for(object.class)
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 do not need to
95
+ # Like {#destroy} but operates on multiple aggregates. Roots must
88
96
  # be of the same type.
89
97
  #
90
- # @param objects [[Object]] Array of roots of the aggregates to be destroyed.
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(objects)
93
- objects.map(&method(:destroy))
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 configure(class_configs)
99
- @configs = MasterConfig.new(class_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 hydrate(db_object, identity_map)
104
- deserialize(db_object, identity_map)
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 serialize(object, mapping)
110
- @traversal.accept_for_domain(object, SerializeVisitor.new(mapping))
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 set_foreign_keys(object, mapping)
119
- @traversal.accept_for_domain(object, PersistenceAssociationVisitor.new(mapping))
120
- end
121
-
122
- def save(object, mapping)
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 deserialize(db_object, identity_map)
135
- @traversal.accept_for_db(db_object, DeserializeVisitor.new(identity_map))
136
- end
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
- def set_associations(db_object, identity_map)
139
- @traversal.accept_for_db(db_object, DomainAssociationVisitor.new(identity_map))
140
- end
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
- def get_unsaved_objects(objects)
143
- objects.find_all { |object| object.id.nil? }
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 nil_out_object_ids(objects)
147
- objects ||= []
148
- objects.each { |object| object.id = nil }
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 initialize(mapping)
157
- @mapping = mapping
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 visit_object(object, config)
161
- serialize(object, config)
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 continue_traversal?(association_config)
165
- association_config.owned
168
+ def configure(class_configs)
169
+ @configs = MasterConfig.new(class_configs)
166
170
  end
167
171
 
168
- def serialize(object, config)
169
- db_object = serialize_object(object, config)
170
- @mapping[object] = db_object
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 = config.find_in_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 visit_belongs_to(parent, child, belongs_to_config)
226
- belongs_to_config.set_foreign_key(@mapping[parent], child)
227
- end
228
-
229
- def visit_has_one(parent, child, has_one_config)
230
- return unless has_one_config.owned
231
- has_one_config.set_foreign_key(@mapping[child], parent)
232
- end
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 continue_traversal?(association_config)
242
- association_config.owned
243
- end
244
- end
245
-
246
- # @private
247
- class SaveVisitor
248
- include AggregateVisitorTemplate
249
-
250
- def initialize(mapping)
251
- @mapping = mapping
252
- end
253
-
254
- def visit_object(object, config)
255
- config.save(@mapping[object])
256
- end
257
-
258
- def continue_traversal?(association_config)
259
- association_config.owned
260
- end
261
- end
262
-
263
- # @private
264
- class AggregateDiffVisitor
265
- include AggregateVisitorTemplate
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
- private
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
- def associate_one_to_many(db_object, db_children, has_many_config)
327
- parent = @identity_map.get(db_object)
328
- children = @identity_map.map(db_children)
329
- has_many_config.set_children(parent, children)
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 associate_one_to_one(db_parent, db_child, belongs_to_config)
333
- parent = @identity_map.get(db_parent)
334
- child = @identity_map.get(db_child)
335
- belongs_to_config.set_child(parent, child)
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 visit_object(object, config)
344
- config.destroy(object)
258
+ def get_unsaved_objects(objects)
259
+ objects.find_all { |object| object.id.nil? }
345
260
  end
346
261
 
347
- def continue_traversal?(association_config)
348
- association_config.owned
262
+ def nil_out_object_ids(objects)
263
+ objects ||= []
264
+ objects.each { |object| object.id = nil }
349
265
  end
350
266
  end
351
267