vorpal 0.0.7.rc1 → 0.0.7.rc2

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.
@@ -0,0 +1,241 @@
1
+ require 'vorpal/identity_map'
2
+ require 'vorpal/aggregate_utils'
3
+ require 'vorpal/db_loader'
4
+ require 'vorpal/db_driver'
5
+ require 'vorpal/aggregate_repository'
6
+
7
+ module Vorpal
8
+ class Engine
9
+ # @private
10
+ def initialize(db_driver, master_config)
11
+ @db_driver = db_driver
12
+ @configs = master_config
13
+ end
14
+
15
+ # Creates a repository for saving/updating/loading/destroying an aggregate to/from
16
+ # the DB. It is possible to use the methods directly on the {Engine}.
17
+ #
18
+ # @param domain_class [Class] Class of the root of the aggregate.
19
+ # @return [AggregateRepository] Repository suitable for mapping a single aggregate.
20
+ def repository_for(domain_class)
21
+ AggregateRepository.new(domain_class, self)
22
+ end
23
+
24
+ # Try to use {AggregateRepository#persist} instead.
25
+ def persist(roots)
26
+ roots = wrap(roots)
27
+ return roots if roots.empty?
28
+ raise InvalidAggregateRoot, 'Nil aggregate roots are not allowed.' if roots.any?(&:nil?)
29
+
30
+ all_owned_objects = all_owned_objects(roots)
31
+ mapping = {}
32
+ loaded_db_objects = load_owned_from_db(roots.map(&:id).compact, roots.first.class)
33
+
34
+ serialize(all_owned_objects, mapping, loaded_db_objects)
35
+ new_objects = get_unsaved_objects(mapping.keys)
36
+ begin
37
+ set_primary_keys(all_owned_objects, mapping)
38
+ set_foreign_keys(all_owned_objects, mapping)
39
+ remove_orphans(mapping, loaded_db_objects)
40
+ save(all_owned_objects, new_objects, mapping)
41
+
42
+ return roots
43
+ rescue Exception
44
+ nil_out_object_ids(new_objects)
45
+ raise
46
+ end
47
+ end
48
+
49
+ # Try to use {AggregateRepository#load_one} instead.
50
+ def load_one(db_root, domain_class, identity_map)
51
+ load_many(Array(db_root), domain_class, identity_map).first
52
+ end
53
+
54
+ # Try to use {AggregateRepository#load_many} instead.
55
+ def load_many(db_roots, domain_class, identity_map)
56
+ raise InvalidAggregateRoot, 'Nil aggregate roots are not allowed.' if db_roots.any?(&:nil?)
57
+
58
+ loaded_db_objects = DbLoader.new(false, @db_driver).load_from_db_objects(db_roots, @configs.config_for(domain_class))
59
+ deserialize(loaded_db_objects, identity_map)
60
+ set_associations(loaded_db_objects, identity_map)
61
+
62
+ db_roots.map { |db_object| identity_map.get(db_object) }
63
+ end
64
+
65
+ # Try to use {AggregateRepository#destroy} instead.
66
+ def destroy(roots)
67
+ roots = wrap(roots)
68
+ return roots if roots.empty?
69
+ raise InvalidAggregateRoot, 'Nil aggregate roots are not allowed.' if roots.any?(&:nil?)
70
+
71
+ destroy_by_id(roots.map(&:id), roots.first.class)
72
+ roots
73
+ end
74
+
75
+ # Try to use {AggregateRepository#destroy_by_id} instead.
76
+ def destroy_by_id(ids, domain_class)
77
+ ids = wrap(ids)
78
+ raise InvalidPrimaryKeyValue, 'Nil primary key values are not allowed.' if ids.any?(&:nil?)
79
+
80
+ loaded_db_objects = load_owned_from_db(ids, domain_class)
81
+ loaded_db_objects.each do |config, db_objects|
82
+ @db_driver.destroy(config, db_objects.map(&:id))
83
+ end
84
+ ids
85
+ end
86
+
87
+ # Try to use {AggregateRepository#db_class} instead.
88
+ def db_class(domain_class)
89
+ @configs.config_for(domain_class).db_class
90
+ end
91
+
92
+ def query(domain_class)
93
+ @db_driver.query(@configs.config_for(domain_class))
94
+ end
95
+
96
+ private
97
+
98
+ def wrap(collection_or_not)
99
+ if collection_or_not.is_a?(Array)
100
+ collection_or_not
101
+ else
102
+ [collection_or_not]
103
+ end
104
+ end
105
+
106
+ def all_owned_objects(roots)
107
+ AggregateUtils.group_by_type(roots, @configs)
108
+ end
109
+
110
+ def load_from_db(ids, domain_class, only_owned=false)
111
+ DbLoader.new(only_owned, @db_driver).load_from_db(ids, @configs.config_for(domain_class))
112
+ end
113
+
114
+ def load_owned_from_db(ids, domain_class)
115
+ load_from_db(ids, domain_class, true)
116
+ end
117
+
118
+ def deserialize(loaded_db_objects, identity_map)
119
+ loaded_db_objects.flat_map do |config, db_objects|
120
+ db_objects.map do |db_object|
121
+ # TODO: There is a bug here when you have something in the IdentityMap that is stale and needs to be updated.
122
+ identity_map.get_and_set(db_object) { config.deserialize(db_object) }
123
+ end
124
+ end
125
+ end
126
+
127
+ def set_associations(loaded_db_objects, identity_map)
128
+ loaded_db_objects.each do |config, db_objects|
129
+ db_objects.each do |db_object|
130
+ config.local_association_configs.each do |association_config|
131
+ db_remote = loaded_db_objects.find_by_id(
132
+ association_config.remote_class_config(db_object),
133
+ association_config.fk_value(db_object)
134
+ )
135
+ association_config.associate(identity_map.get(db_object), identity_map.get(db_remote))
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ def serialize(owned_objects, mapping, loaded_db_objects)
142
+ owned_objects.each do |config, objects|
143
+ objects.each do |object|
144
+ db_object = serialize_object(object, config, loaded_db_objects)
145
+ mapping[object] = db_object
146
+ end
147
+ end
148
+ end
149
+
150
+ def serialize_object(object, config, loaded_db_objects)
151
+ if config.serialization_required?
152
+ attributes = config.serialize(object)
153
+ if object.id.nil?
154
+ config.build_db_object(attributes)
155
+ else
156
+ db_object = loaded_db_objects.find_by_id(config, object.id)
157
+ config.set_db_object_attributes(db_object, attributes)
158
+ db_object
159
+ end
160
+ else
161
+ object
162
+ end
163
+ end
164
+
165
+ def set_primary_keys(owned_objects, mapping)
166
+ owned_objects.each do |config, objects|
167
+ in_need_of_primary_keys = objects.find_all { |obj| obj.id.nil? }
168
+ primary_keys = @db_driver.get_primary_keys(config, in_need_of_primary_keys.length)
169
+ in_need_of_primary_keys.zip(primary_keys).each do |object, primary_key|
170
+ mapping[object].id = primary_key
171
+ object.id = primary_key
172
+ end
173
+ end
174
+ mapping.rehash # needs to happen because setting the id on an AR::Base model changes its hash value
175
+ end
176
+
177
+ def set_foreign_keys(owned_objects, mapping)
178
+ owned_objects.each do |config, objects|
179
+ objects.each do |object|
180
+ config.has_manys.each do |has_many_config|
181
+ if has_many_config.owned
182
+ children = has_many_config.get_children(object)
183
+ children.each do |child|
184
+ has_many_config.set_foreign_key(mapping[child], object)
185
+ end
186
+ end
187
+ end
188
+
189
+ config.has_ones.each do |has_one_config|
190
+ if has_one_config.owned
191
+ child = has_one_config.get_child(object)
192
+ has_one_config.set_foreign_key(mapping[child], object)
193
+ end
194
+ end
195
+
196
+ config.belongs_tos.each do |belongs_to_config|
197
+ child = belongs_to_config.get_child(object)
198
+ belongs_to_config.set_foreign_key(mapping[object], child)
199
+ end
200
+ end
201
+ end
202
+ end
203
+
204
+ def save(owned_objects, new_objects, mapping)
205
+ grouped_new_objects = new_objects.group_by { |obj| @configs.config_for(obj.class) }
206
+ owned_objects.each do |config, objects|
207
+ objects_to_insert = grouped_new_objects[config] || []
208
+ db_objects_to_insert = objects_to_insert.map { |obj| mapping[obj] }
209
+ @db_driver.insert(config, db_objects_to_insert)
210
+
211
+ objects_to_update = objects - objects_to_insert
212
+ db_objects_to_update = objects_to_update.map { |obj| mapping[obj] }
213
+ @db_driver.update(config, db_objects_to_update)
214
+ end
215
+ end
216
+
217
+ def remove_orphans(mapping, loaded_db_objects)
218
+ db_objects_in_aggregate = mapping.values
219
+ db_objects_in_db = loaded_db_objects.all_objects
220
+ all_orphans = db_objects_in_db - db_objects_in_aggregate
221
+ grouped_orphans = all_orphans.group_by { |o| @configs.config_for_db_object(o) }
222
+ grouped_orphans.each do |config, orphans|
223
+ @db_driver.destroy(config, orphans)
224
+ end
225
+ end
226
+
227
+ def get_unsaved_objects(objects)
228
+ objects.find_all { |object| object.id.nil? }
229
+ end
230
+
231
+ def nil_out_object_ids(objects)
232
+ objects ||= []
233
+ objects.each { |object| object.id = nil }
234
+ end
235
+ end
236
+
237
+ class InvalidPrimaryKeyValue < StandardError
238
+ end
239
+ class InvalidAggregateRoot < StandardError
240
+ end
241
+ end
@@ -1,3 +1,3 @@
1
1
  module Vorpal
2
- VERSION = "0.0.7.rc1"
2
+ VERSION = "0.0.7.rc2"
3
3
  end
data/lib/vorpal.rb CHANGED
@@ -1,11 +1,10 @@
1
1
  require "vorpal/version"
2
2
  require "vorpal/configuration"
3
3
 
4
- # Allows easy creation of {Vorpal::AggregateRepository}
5
- # instances.
4
+ # Allows easy creation of {Vorpal::Engine} instances.
6
5
  #
7
6
  # ```ruby
8
- # repository = Vorpal.define do
7
+ # engine = Vorpal.define do
9
8
  # map Tree do
10
9
  # attributes :name
11
10
  # belongs_to :trunk
@@ -22,6 +21,8 @@ require "vorpal/configuration"
22
21
  # belongs_to :tree
23
22
  # end
24
23
  # end
24
+ #
25
+ # repository = engine.repository_for(Tree)
25
26
  # ```
26
27
  module Vorpal
27
28
  extend Vorpal::Configuration