vorpal 0.0.6.rc4 → 0.0.6

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
- OGZhMWQ2MGM3ZWZlZTJhZjkxOGVlMmZiYTkzODk4NTljNzk5NjU0MA==
4
+ MzY2ZTAwNWJiYmFlY2Y5NGZkZjg4ZTUyMGU4M2U4NjNiNzkxNzdkMg==
5
5
  data.tar.gz: !binary |-
6
- ZDMxYTEyMzI5OTQ0YmJkNDJmNjg3OGZjNzc5NDE2ZGUyNjc0M2Y4ZA==
6
+ YTQ0OGZiZGIwOTdmMjcyYmRjZTVmOWRjZGI0ODgzOWRkZTI5OWUxNQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- M2QxM2JlYWQ0NGE5Njg3YWZhYmI4MjdmNmVlZjVhZGIzOTA1YTZjMDYzYzVj
10
- ZGViNjM3ZDI2YTY5MGNkZGRhZmNkMmI2YjUxZDYwYTVjYmI1ZjA5ZmVmNTgx
11
- ZGIxZTZjZTQzYTQ2NzQ4ZWMxNWM2OThmYzIzNjAzMjYxNTVkMTE=
9
+ ZDBiYjBhZDk0ZjY3MjJmMWRjZDcwMWQ3YzAxMGRhYzNiNjkzZjZlMzk5MmIz
10
+ ZWVmY2M1YjBiY2NhY2IyZmEyOThkMjA5MGI5YzUzZGJkMTU3Mzg0NWU4NGY5
11
+ Zjc3OGVhMmE1ZTExMzg1YWI1MjhlMjU2NjI2MTdjNzQzZGY1MzU=
12
12
  data.tar.gz: !binary |-
13
- ZmExNTU3NGNiYmM1YjIxZjg3M2VlNjU0ODJmY2NjNmZlYTRkMDcyZTYzZTNm
14
- YTkyZDUxMTI2NGI4MzhjYzg5NjBiMWNmNGUwOTI1ZWIzZDc5MDRiM2RmMmQx
15
- MzEzYmI0ZWRmYzYyZjgyYjFmOTI5NDA3YTYzMzJlYmZkMzc1YzU=
13
+ NTNlMTA1NjRiYzc1ODVhNGU5MGNlODIyYTNmOGY0YTQxNTBiMTYzN2UwMzdm
14
+ YTk0MTIyNTAxZmNhMzY3MTk5YjEyM2M1ZmIzNTIzMzU2M2FlZWQ2YjM4OGZj
15
+ MTU4YjU5ZTlkZmIzZTMzMjAzMWMxNWQ2OGY5YTI5MGZiZTQ3Y2Q=
data/README.md CHANGED
@@ -72,7 +72,7 @@ class Tree
72
72
  end
73
73
  ```
74
74
 
75
- In this aggregate, the Tree is the root and the Branches are inside the aggregate boundary. The Gardener is not technically part of the aggregate but is required for the aggregate to make sense so we say that it is on the aggregate boundary.
75
+ In this aggregate, the Tree is the root and the Branches are inside the aggregate boundary. The Gardener is not technically part of the aggregate but is required for the aggregate to make sense so we say that it is on the aggregate boundary. Only objects that are inside the aggregate boundary will be saved, updated, or destroyed by Vorpal.
76
76
 
77
77
  POROs must have setters and getters for all fields and associations that are to be persisted. They must also provide a no argument constructor.
78
78
 
@@ -143,6 +143,10 @@ module TreeRepository
143
143
  def destroy(tree)
144
144
  @repository.destroy(tree)
145
145
  end
146
+
147
+ def destroy_by_id(tree_id)
148
+ @repository.destroy_by_id(tree_id, Tree)
149
+ end
146
150
  end
147
151
  ```
148
152
 
@@ -162,6 +166,9 @@ small_tree = TreeRepository.find(small_tree_id)
162
166
  # Destroys the given Tree as well as all Branches referenced by it,
163
167
  # but not Gardeners.
164
168
  TreeRepository.destroy(dead_tree)
169
+
170
+ # Or
171
+ TreeRepository.destroy_by_id(dead_tree_id)
165
172
  ```
166
173
 
167
174
  ## API Documentation
@@ -188,9 +195,9 @@ It also does not do some things that you might expect from other ORMs:
188
195
  * Support for other ORMs.
189
196
  * Value objects.
190
197
  * Remove dependency on ActiveRecord (optimistic locking? updated_at, created_at support? Data type conversions? TimeZone support?)
191
- * More efficient object loading and persisting (use fewer queries.)
198
+ * More efficient updates (use fewer queries.)
192
199
  * Nicer DSL for specifying field that have different names in the domain model than in the DB.
193
-
200
+
194
201
  ## FAQ
195
202
 
196
203
  **Q.** Why do I care about separating my persistence mechanism from my domain models?
@@ -4,240 +4,277 @@ require 'vorpal/db_loader'
4
4
  require 'vorpal/db_driver'
5
5
 
6
6
  module Vorpal
7
- class AggregateRepository
8
- # @private
9
- def initialize(db_driver, master_config)
10
- @db_driver = db_driver
11
- @configs = master_config
12
- end
7
+ class AggregateRepository
8
+ # @private
9
+ def initialize(db_driver, master_config)
10
+ @db_driver = db_driver
11
+ @configs = master_config
12
+ end
13
13
 
14
- # Saves an aggregate to the DB. Inserts objects that are new to the
15
- # aggregate, updates existing objects and deletes objects that are no longer
16
- # present.
17
- #
18
- # Objects that are on the boundary of the aggregate (owned: false) will not
19
- # be inserted, updated, or deleted. However, the relationships to these
20
- # objects (provided they are stored within the aggregate) will be saved.
21
- #
22
- # @param root [Object] Root of the aggregate to be saved.
23
- # @return [Object] Root of the aggregate.
24
- def persist(root)
25
- persist_all(Array(root)).first
26
- end
14
+ # Saves an aggregate to the DB. Inserts objects that are new to the
15
+ # aggregate, updates existing objects and deletes objects that are no longer
16
+ # present.
17
+ #
18
+ # Objects that are on the boundary of the aggregate (owned: false) will not
19
+ # be inserted, updated, or deleted. However, the relationships to these
20
+ # objects (provided they are stored within the aggregate) will be saved.
21
+ #
22
+ # @param root [Object] Root of the aggregate to be saved.
23
+ # @return [Object] Root of the aggregate.
24
+ def persist(root)
25
+ persist_all([root]).first
26
+ end
27
27
 
28
- # Like {#persist} but operates on multiple aggregates. Roots must
29
- # be of the same type.
30
- #
31
- # @param roots [[Object]] array of aggregate roots to be saved.
32
- # @return [[Object]] array of aggregate roots.
33
- def persist_all(roots)
34
- return roots if roots.empty?
35
-
36
- all_owned_objects = all_owned_objects(roots)
37
- mapping = {}
38
- loaded_db_objects = load_owned_from_db(roots.map(&:id), roots.first.class)
39
-
40
- serialize(all_owned_objects, mapping, loaded_db_objects)
41
- new_objects = get_unsaved_objects(mapping.keys)
42
- begin
43
- set_primary_keys(all_owned_objects, mapping)
44
- set_foreign_keys(all_owned_objects, mapping)
45
- remove_orphans(mapping, loaded_db_objects)
46
- save(all_owned_objects, new_objects, mapping)
47
-
48
- return roots
49
- rescue
50
- nil_out_object_ids(new_objects)
51
- raise
28
+ # Like {#persist} but operates on multiple aggregates. Roots must
29
+ # be of the same type.
30
+ #
31
+ # @param roots [[Object]] array of aggregate roots to be saved.
32
+ # @return [[Object]] array of aggregate roots.
33
+ def persist_all(roots)
34
+ return roots if roots.empty?
35
+ raise InvalidAggregateRoot, 'Nil aggregate roots are not allowed.' if roots.any?(&:nil?)
36
+
37
+ all_owned_objects = all_owned_objects(roots)
38
+ mapping = {}
39
+ loaded_db_objects = load_owned_from_db(roots.map(&:id).compact, roots.first.class)
40
+
41
+ serialize(all_owned_objects, mapping, loaded_db_objects)
42
+ new_objects = get_unsaved_objects(mapping.keys)
43
+ begin
44
+ set_primary_keys(all_owned_objects, mapping)
45
+ set_foreign_keys(all_owned_objects, mapping)
46
+ remove_orphans(mapping, loaded_db_objects)
47
+ save(all_owned_objects, new_objects, mapping)
48
+
49
+ return roots
50
+ rescue Exception
51
+ nil_out_object_ids(new_objects)
52
+ raise
53
+ end
52
54
  end
53
- end
54
55
 
55
- # Loads an aggregate from the DB. Will eagerly load all objects in the
56
- # aggregate and on the boundary (owned: false).
57
- #
58
- # @param id [Integer] Primary key value of the root of the aggregate to be
59
- # loaded.
60
- # @param domain_class [Class] Type of the root of the aggregate to
61
- # be loaded.
62
- # @param identity_map [Vorpal::IdentityMap] Provide your own IdentityMap instance
63
- # if you want entity id - unique object mapping for a greater scope than one
64
- # operation.
65
- # @return [Object] Entity with the given primary key value and type.
66
- def load(id, domain_class, identity_map=IdentityMap.new)
67
- load_all([id], domain_class, identity_map).first
68
- end
56
+ # Loads an aggregate from the DB. Will eagerly load all objects in the
57
+ # aggregate and on the boundary (owned: false).
58
+ #
59
+ # @param id [Integer] Primary key value of the root of the aggregate to be
60
+ # loaded.
61
+ # @param domain_class [Class] Type of the root of the aggregate to
62
+ # be loaded.
63
+ # @param identity_map [Vorpal::IdentityMap] Provide your own IdentityMap instance
64
+ # if you want entity id - unique object mapping for a greater scope than one
65
+ # operation.
66
+ # @return [Object] Entity with the given primary key value and type.
67
+ def load(id, domain_class, identity_map=IdentityMap.new)
68
+ load_all([id], domain_class, identity_map).first
69
+ end
69
70
 
70
- # Like {#load} but operates on multiple ids.
71
- #
72
- # @param ids [[Integer]] Array of primary key values of the roots of the
73
- # aggregates to be loaded.
74
- # @param domain_class [Class] Type of the roots of the aggregate to be loaded.
75
- # @param identity_map [Vorpal::IdentityMap] Provide your own IdentityMap instance
76
- # if you want entity id - unique object mapping for a greater scope than one
77
- # operation.
78
- # @return [[Object]] Entities with the given primary key values and type.
79
- def load_all(ids, domain_class, identity_map=IdentityMap.new)
80
- loaded_db_objects = load_from_db(ids, domain_class)
81
- objects = deserialize(loaded_db_objects, identity_map)
82
- set_associations(loaded_db_objects, identity_map)
83
-
84
- objects.select { |obj| obj.class == domain_class }
85
- end
71
+ # Like {#load} but operates on multiple ids.
72
+ #
73
+ # @param ids [[Integer]] Array of primary key values of the roots of the
74
+ # aggregates to be loaded.
75
+ # @param domain_class [Class] Type of the roots of the aggregate to be loaded.
76
+ # @param identity_map [Vorpal::IdentityMap] Provide your own IdentityMap instance
77
+ # if you want entity id - unique object mapping for a greater scope than one
78
+ # operation.
79
+ # @return [[Object]] Entities with the given primary key values and type.
80
+ def load_all(ids, domain_class, identity_map=IdentityMap.new)
81
+ raise InvalidPrimaryKeyValue, 'Nil primary key values are not allowed.' if ids.any?(&:nil?)
86
82
 
87
- # Removes an aggregate from the DB. Even if the aggregate contains unsaved
88
- # changes this method will correctly remove everything.
89
- #
90
- # @param root [Object] Root of the aggregate to be destroyed.
91
- # @return [Object] Root that was passed in.
92
- def destroy(root)
93
- destroy_all(Array(root)).first
94
- end
83
+ loaded_db_objects = load_from_db(ids, domain_class)
84
+ objects = deserialize(loaded_db_objects, identity_map)
85
+ set_associations(loaded_db_objects, identity_map)
95
86
 
96
- # Like {#destroy} but operates on multiple aggregates. Roots must
97
- # be of the same type.
98
- #
99
- # @param roots [[Object]] Array of roots of the aggregates to be destroyed.
100
- # @return [[Object]] Roots that were passed in.
101
- def destroy_all(roots)
102
- return roots if roots.empty?
103
- loaded_db_objects = load_owned_from_db(roots.map(&:id), roots.first.class)
104
- loaded_db_objects.each do |config, db_objects|
105
- @db_driver.destroy(config, db_objects)
106
- end
107
- roots
108
- end
87
+ sorted_roots(ids, objects, domain_class)
88
+ end
109
89
 
110
- private
90
+ # Removes an aggregate from the DB. Even if the aggregate contains unsaved
91
+ # changes this method will correctly remove everything.
92
+ #
93
+ # @param root [Object] Root of the aggregate to be destroyed.
94
+ # @return [Object] Root that was passed in.
95
+ def destroy(root)
96
+ destroy_all([root]).first
97
+ end
111
98
 
112
- def all_owned_objects(roots)
113
- AggregateUtils.group_by_type(roots, @configs)
114
- end
99
+ # Like {#destroy} but operates on multiple aggregates. Roots must
100
+ # be of the same type.
101
+ #
102
+ # @param roots [[Object]] Array of roots of the aggregates to be destroyed.
103
+ # @return [[Object]] Roots that were passed in.
104
+ def destroy_all(roots)
105
+ return roots if roots.empty?
106
+ raise InvalidAggregateRoot, 'Nil aggregate roots are not allowed.' if roots.any?(&:nil?)
115
107
 
116
- def load_from_db(ids, domain_class, only_owned=false)
117
- DbLoader.new(only_owned, @db_driver).load_from_db(ids, @configs.config_for(domain_class))
118
- end
108
+ destroy_all_by_id(roots.map(&:id), roots.first.class)
109
+ roots
110
+ end
119
111
 
120
- def load_owned_from_db(ids, domain_class)
121
- load_from_db(ids, domain_class, true)
122
- end
112
+ # Removes an aggregate from the DB given its primary key.
113
+ #
114
+ # @param id [Integer] Id of root of the aggregate to be destroyed.
115
+ # @param domain_class [Class] Type of the root of the aggregate to
116
+ # be destroyed.
117
+ def destroy_by_id(id, domain_class)
118
+ destroy_all_by_id([id], domain_class)
119
+ end
123
120
 
124
- def deserialize(loaded_db_objects, identity_map)
125
- loaded_db_objects.flat_map do |config, db_objects|
126
- db_objects.map do |db_object|
127
- # TODO: There is a bug here when you have something in the IdentityMap that is stale and needs to be updated.
128
- identity_map.get_and_set(db_object) { config.deserialize(db_object) }
121
+ # Like {#destroy_by_id} but operates on multiple ids. Roots must
122
+ # be of the same type.
123
+ #
124
+ # @param ids [[Integer]] Ids of roots of the aggregates to be destroyed.
125
+ # @param domain_class [Class] Type of the roots of the aggregates to
126
+ # be destroyed.
127
+ def destroy_all_by_id(ids, domain_class)
128
+ raise InvalidPrimaryKeyValue, 'Nil primary key values are not allowed.' if ids.any?(&:nil?)
129
+
130
+ loaded_db_objects = load_owned_from_db(ids, domain_class)
131
+ loaded_db_objects.each do |config, db_objects|
132
+ @db_driver.destroy(config, db_objects.map(&:id))
129
133
  end
134
+ ids
135
+ end
136
+
137
+ private
138
+
139
+ def all_owned_objects(roots)
140
+ AggregateUtils.group_by_type(roots, @configs)
141
+ end
142
+
143
+ def load_from_db(ids, domain_class, only_owned=false)
144
+ DbLoader.new(only_owned, @db_driver).load_from_db(ids, @configs.config_for(domain_class))
145
+ end
146
+
147
+ def load_owned_from_db(ids, domain_class)
148
+ load_from_db(ids, domain_class, true)
130
149
  end
131
- end
132
150
 
133
- def set_associations(loaded_db_objects, identity_map)
134
- loaded_db_objects.each do |config, db_objects|
135
- db_objects.each do |db_object|
136
- config.local_association_configs.each do |association_config|
137
- db_remote = loaded_db_objects.find_by_id(
138
- association_config.remote_class_config(db_object),
139
- association_config.fk_value(db_object)
140
- )
141
- association_config.associate(identity_map.get(db_object), identity_map.get(db_remote))
151
+ def deserialize(loaded_db_objects, identity_map)
152
+ loaded_db_objects.flat_map do |config, db_objects|
153
+ db_objects.map do |db_object|
154
+ # TODO: There is a bug here when you have something in the IdentityMap that is stale and needs to be updated.
155
+ identity_map.get_and_set(db_object) { config.deserialize(db_object) }
142
156
  end
143
157
  end
144
158
  end
145
- end
146
159
 
147
- def serialize(owned_objects, mapping, loaded_db_objects)
148
- owned_objects.each do |config, objects|
149
- objects.each do |object|
150
- db_object = serialize_object(object, config, loaded_db_objects)
151
- mapping[object] = db_object
160
+ def set_associations(loaded_db_objects, identity_map)
161
+ loaded_db_objects.each do |config, db_objects|
162
+ db_objects.each do |db_object|
163
+ config.local_association_configs.each do |association_config|
164
+ db_remote = loaded_db_objects.find_by_id(
165
+ association_config.remote_class_config(db_object),
166
+ association_config.fk_value(db_object)
167
+ )
168
+ association_config.associate(identity_map.get(db_object), identity_map.get(db_remote))
169
+ end
170
+ end
152
171
  end
153
172
  end
154
- end
155
173
 
156
- def serialize_object(object, config, loaded_db_objects)
157
- if config.serialization_required?
158
- attributes = config.serialize(object)
159
- if object.id.nil?
160
- config.build_db_object(attributes)
174
+ def sorted_roots(ids, objects, domain_class)
175
+ roots = objects.select { |obj| obj.class == domain_class }
176
+ roots_by_id = roots.reduce({}) { |h, root| h[root.id] = root; h }
177
+ ids.map { |id| roots_by_id[id] }.compact
178
+ end
179
+
180
+ def serialize(owned_objects, mapping, loaded_db_objects)
181
+ owned_objects.each do |config, objects|
182
+ objects.each do |object|
183
+ db_object = serialize_object(object, config, loaded_db_objects)
184
+ mapping[object] = db_object
185
+ end
186
+ end
187
+ end
188
+
189
+ def serialize_object(object, config, loaded_db_objects)
190
+ if config.serialization_required?
191
+ attributes = config.serialize(object)
192
+ if object.id.nil?
193
+ config.build_db_object(attributes)
194
+ else
195
+ db_object = loaded_db_objects.find_by_id(config, object.id)
196
+ config.set_db_object_attributes(db_object, attributes)
197
+ db_object
198
+ end
161
199
  else
162
- db_object = loaded_db_objects.find_by_id(config, object.id)
163
- config.set_db_object_attributes(db_object, attributes)
164
- db_object
200
+ object
165
201
  end
166
- else
167
- object
168
202
  end
169
- end
170
203
 
171
- def set_primary_keys(owned_objects, mapping)
172
- owned_objects.each do |config, objects|
173
- in_need_of_primary_keys = objects.find_all { |obj| obj.id.nil? }
174
- primary_keys = @db_driver.get_primary_keys(config, in_need_of_primary_keys.length)
175
- in_need_of_primary_keys.zip(primary_keys).each do |object, primary_key|
176
- mapping[object].id = primary_key
177
- object.id = primary_key
204
+ def set_primary_keys(owned_objects, mapping)
205
+ owned_objects.each do |config, objects|
206
+ in_need_of_primary_keys = objects.find_all { |obj| obj.id.nil? }
207
+ primary_keys = @db_driver.get_primary_keys(config, in_need_of_primary_keys.length)
208
+ in_need_of_primary_keys.zip(primary_keys).each do |object, primary_key|
209
+ mapping[object].id = primary_key
210
+ object.id = primary_key
211
+ end
178
212
  end
213
+ mapping.rehash # needs to happen because setting the id on an AR::Base model changes its hash value
179
214
  end
180
- mapping.rehash # needs to happen because setting the id on an AR::Base model changes its hash value
181
- end
182
215
 
183
- def set_foreign_keys(owned_objects, mapping)
184
- owned_objects.each do |config, objects|
185
- objects.each do |object|
186
- config.has_manys.each do |has_many_config|
187
- if has_many_config.owned
188
- children = has_many_config.get_children(object)
189
- children.each do |child|
190
- has_many_config.set_foreign_key(mapping[child], object)
216
+ def set_foreign_keys(owned_objects, mapping)
217
+ owned_objects.each do |config, objects|
218
+ objects.each do |object|
219
+ config.has_manys.each do |has_many_config|
220
+ if has_many_config.owned
221
+ children = has_many_config.get_children(object)
222
+ children.each do |child|
223
+ has_many_config.set_foreign_key(mapping[child], object)
224
+ end
191
225
  end
192
226
  end
193
- end
194
227
 
195
- config.has_ones.each do |has_one_config|
196
- if has_one_config.owned
197
- child = has_one_config.get_child(object)
198
- has_one_config.set_foreign_key(mapping[child], object)
228
+ config.has_ones.each do |has_one_config|
229
+ if has_one_config.owned
230
+ child = has_one_config.get_child(object)
231
+ has_one_config.set_foreign_key(mapping[child], object)
232
+ end
199
233
  end
200
- end
201
234
 
202
- config.belongs_tos.each do |belongs_to_config|
203
- child = belongs_to_config.get_child(object)
204
- belongs_to_config.set_foreign_key(mapping[object], child)
235
+ config.belongs_tos.each do |belongs_to_config|
236
+ child = belongs_to_config.get_child(object)
237
+ belongs_to_config.set_foreign_key(mapping[object], child)
238
+ end
205
239
  end
206
240
  end
207
241
  end
208
- end
209
242
 
210
- def save(owned_objects, new_objects, mapping)
211
- grouped_new_objects = new_objects.group_by { |obj| @configs.config_for(obj.class) }
212
- owned_objects.each do |config, objects|
213
- objects_to_insert = grouped_new_objects[config] || []
214
- db_objects_to_insert = objects_to_insert.map { |obj| mapping[obj] }
215
- @db_driver.insert(config, db_objects_to_insert)
243
+ def save(owned_objects, new_objects, mapping)
244
+ grouped_new_objects = new_objects.group_by { |obj| @configs.config_for(obj.class) }
245
+ owned_objects.each do |config, objects|
246
+ objects_to_insert = grouped_new_objects[config] || []
247
+ db_objects_to_insert = objects_to_insert.map { |obj| mapping[obj] }
248
+ @db_driver.insert(config, db_objects_to_insert)
216
249
 
217
- objects_to_update = objects - objects_to_insert
218
- db_objects_to_update = objects_to_update.map { |obj| mapping[obj] }
219
- @db_driver.update(config, db_objects_to_update)
250
+ objects_to_update = objects - objects_to_insert
251
+ db_objects_to_update = objects_to_update.map { |obj| mapping[obj] }
252
+ @db_driver.update(config, db_objects_to_update)
253
+ end
254
+ end
255
+
256
+ def remove_orphans(mapping, loaded_db_objects)
257
+ db_objects_in_aggregate = mapping.values
258
+ db_objects_in_db = loaded_db_objects.all_objects
259
+ all_orphans = db_objects_in_db - db_objects_in_aggregate
260
+ grouped_orphans = all_orphans.group_by { |o| @configs.config_for_db_object(o) }
261
+ grouped_orphans.each do |config, orphans|
262
+ @db_driver.destroy(config, orphans)
263
+ end
220
264
  end
221
- end
222
265
 
223
- def remove_orphans(mapping, loaded_db_objects)
224
- db_objects_in_aggregate = mapping.values
225
- db_objects_in_db = loaded_db_objects.all_objects
226
- all_orphans = db_objects_in_db - db_objects_in_aggregate
227
- grouped_orphans = all_orphans.group_by { |o| @configs.config_for_db_object(o) }
228
- grouped_orphans.each do |config, orphans|
229
- @db_driver.destroy(config, orphans)
266
+ def get_unsaved_objects(objects)
267
+ objects.find_all { |object| object.id.nil? }
230
268
  end
231
- end
232
269
 
233
- def get_unsaved_objects(objects)
234
- objects.find_all { |object| object.id.nil? }
270
+ def nil_out_object_ids(objects)
271
+ objects ||= []
272
+ objects.each { |object| object.id = nil }
273
+ end
235
274
  end
236
275
 
237
- def nil_out_object_ids(objects)
238
- objects ||= []
239
- objects.each { |object| object.id = nil }
276
+ class InvalidPrimaryKeyValue < StandardError
277
+ end
278
+ class InvalidAggregateRoot < StandardError
240
279
  end
241
- end
242
-
243
280
  end