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 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