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.
@@ -0,0 +1,50 @@
1
+ module Vorpal
2
+ # @private
3
+ class AggregateTraversal
4
+ def initialize(configs)
5
+ @configs = configs
6
+ end
7
+
8
+ # Traversal should always begin with an object that is known to be
9
+ # able to reach all other objects in the aggregate (like the root!)
10
+ def accept(object, visitor, already_visited=[])
11
+ return if object.nil?
12
+
13
+ config = @configs.config_for(object.class)
14
+ return if config.nil?
15
+
16
+ return if already_visited.include?(object)
17
+ already_visited << object
18
+
19
+ visitor.visit_object(object, config)
20
+
21
+ config.belongs_tos.each do |belongs_to_config|
22
+ child = belongs_to_config.get_child(object)
23
+ accept(child, visitor, already_visited) if visitor.continue_traversal?(belongs_to_config)
24
+ end
25
+
26
+ config.has_ones.each do |has_one_config|
27
+ child = has_one_config.get_child(object)
28
+ accept(child, visitor, already_visited) if visitor.continue_traversal?(has_one_config)
29
+ end
30
+
31
+ config.has_manys.each do |has_many_config|
32
+ children = has_many_config.get_children(object)
33
+ children.each do |child|
34
+ accept(child, visitor, already_visited) if visitor.continue_traversal?(has_many_config)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ # @private
41
+ module AggregateVisitorTemplate
42
+ def visit_object(object, config)
43
+ # override me!
44
+ end
45
+
46
+ def continue_traversal?(association_config)
47
+ true
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,37 @@
1
+ require 'vorpal/aggregate_traversal'
2
+
3
+ module Vorpal
4
+ module AggregateUtils
5
+ extend self
6
+
7
+ def group_by_type(roots, configs)
8
+ traversal = AggregateTraversal.new(configs)
9
+
10
+ all = roots.flat_map do |root|
11
+ owned_object_visitor = OwnedObjectVisitor.new
12
+ traversal.accept(root, owned_object_visitor)
13
+ owned_object_visitor.owned_objects
14
+ end
15
+
16
+ all.group_by { |obj| configs.config_for(obj.class) }
17
+ end
18
+ end
19
+
20
+ # @private
21
+ class OwnedObjectVisitor
22
+ include AggregateVisitorTemplate
23
+ attr_reader :owned_objects
24
+
25
+ def initialize
26
+ @owned_objects = []
27
+ end
28
+
29
+ def visit_object(object, config)
30
+ @owned_objects << object
31
+ end
32
+
33
+ def continue_traversal?(association_config)
34
+ association_config.owned
35
+ end
36
+ end
37
+ end
@@ -135,13 +135,13 @@ class ConfigBuilder
135
135
 
136
136
  def serializer(attrs)
137
137
  Class.new(SimpleSerializer::Serializer) do
138
- hash_attributes *attrs
138
+ attributes *attrs
139
139
  end
140
140
  end
141
141
 
142
142
  def deserializer(attrs)
143
143
  Class.new(SimpleSerializer::Deserializer) do
144
- object_attributes *attrs
144
+ data_attributes *attrs
145
145
  end
146
146
  end
147
147
  end
@@ -1,4 +1,5 @@
1
- require 'vorpal/hash_initialization'
1
+ require 'vorpal/util/hash_initialization'
2
+ require 'equalizer'
2
3
 
3
4
  module Vorpal
4
5
 
@@ -39,6 +40,7 @@ end
39
40
 
40
41
  # @private
41
42
  class ClassConfig
43
+ include Equalizer.new(:domain_class, :db_class)
42
44
  attr_reader :serializer, :deserializer, :domain_class, :db_class
43
45
  attr_accessor :has_manys, :belongs_tos, :has_ones
44
46
 
@@ -52,33 +54,6 @@ class ClassConfig
52
54
  end
53
55
  end
54
56
 
55
- def get_primary_keys(count)
56
- result = ActiveRecord::Base.connection.execute("select nextval('#{sequence_name}') from generate_series(1,#{count});")
57
- result.column_values(0).map(&:to_i)
58
- end
59
-
60
- def find_in_db(object)
61
- load_by_id(object.id)
62
- end
63
-
64
- def load_by_id(id)
65
- db_class.where(id: id).first
66
- end
67
-
68
- def load_by_foreign_key(id, foreign_key_info)
69
- arel = db_class.where(foreign_key_info.fk_column => id)
70
- arel = arel.where(foreign_key_info.fk_type_column => foreign_key_info.fk_type) if foreign_key_info.polymorphic?
71
- arel.order(:id).all
72
- end
73
-
74
- def destroy(db_object)
75
- db_object.destroy
76
- end
77
-
78
- def save(db_object)
79
- db_object.save!
80
- end
81
-
82
57
  def build_db_object(attributes)
83
58
  db_class.new(attributes)
84
59
  end
@@ -112,15 +87,15 @@ class ClassConfig
112
87
  db_object.send(field)
113
88
  end
114
89
 
115
- private
116
-
117
- def sequence_name
118
- "#{db_class.table_name}_id_seq"
90
+ def table_name
91
+ db_class.table_name
119
92
  end
120
93
  end
121
94
 
122
95
  # @private
123
96
  class ForeignKeyInfo
97
+ include Equalizer.new(:fk_column, :fk_type_column, :fk_type)
98
+
124
99
  attr_reader :fk_column, :fk_type_column, :fk_type, :polymorphic
125
100
 
126
101
  def initialize(fk_column, fk_type_column, fk_type, polymorphic)
@@ -133,6 +108,10 @@ class ForeignKeyInfo
133
108
  def polymorphic?
134
109
  @polymorphic
135
110
  end
111
+
112
+ def matches_polymorphic_type?(db_object)
113
+ db_object.send(fk_type_column) == fk_type
114
+ end
136
115
  end
137
116
 
138
117
  # @private
@@ -157,33 +136,20 @@ class RelationalAssociation
157
136
  local_config.set_field(local_db_model, fk_type, remote_model.class.name) if polymorphic?
158
137
  end
159
138
 
160
- def load_locals(remote_db_model)
161
- id = remote_db_model.id
162
- # TODO: this method should probably be able to determine the config for the remote models
163
- raise "Only supports having one remote configuration when navigating from the remote side to the local side of an association." if remote_configs.size != 1
164
- remote_config = remote_configs.first
165
- local_config.load_by_foreign_key(id, foreign_key_info(remote_config))
166
- end
167
-
168
- def load_remote(local_db_model)
169
- remote_config = polymorphic? ? remote_config_for_local_db_object(local_db_model) : remote_configs.first
170
- remote_config.load_by_id(get_foreign_key(local_db_model))
139
+ def remote_config_for_local_db_object(local_db_model)
140
+ class_name = local_config.get_field(local_db_model, fk_type)
141
+ remote_configs.detect { |config| config.domain_class.name == class_name }
171
142
  end
172
143
 
173
- private
174
-
175
144
  def polymorphic?
176
145
  !fk_type.nil?
177
146
  end
178
147
 
179
- def foreign_key_info(class_config)
180
- ForeignKeyInfo.new(fk, fk_type, class_config.domain_class.name, polymorphic?)
148
+ def foreign_key_info(remote_class_config)
149
+ ForeignKeyInfo.new(fk, fk_type, remote_class_config.domain_class.name, polymorphic?)
181
150
  end
182
151
 
183
- def remote_config_for_local_db_object(local_db_model)
184
- class_name = local_config.get_field(local_db_model, fk_type)
185
- remote_configs.detect { |config| config.domain_class.name == class_name }
186
- end
152
+ private
187
153
 
188
154
  def get_foreign_key(local_db_model)
189
155
  local_config.get_field(local_db_model, fk)
@@ -196,6 +162,7 @@ class HasManyConfig
196
162
  attr_reader :name, :owned, :fk, :fk_type, :child_class
197
163
 
198
164
  def init_relational_association(child_config, parent_config)
165
+ @parent_config = parent_config
199
166
  @relational_association = RelationalAssociation.new(fk: fk, fk_type: fk_type, local_config: child_config, remote_configs: [parent_config])
200
167
  end
201
168
 
@@ -211,36 +178,57 @@ class HasManyConfig
211
178
  @relational_association.set_foreign_key(db_child, parent)
212
179
  end
213
180
 
214
- def load_children(db_parent)
215
- @relational_association.load_locals(db_parent)
181
+ def associated?(db_parent, db_child)
182
+ return false if child_config.db_class != db_child.class
183
+ db_child.send(fk) == db_parent.id
216
184
  end
217
- end
218
185
 
219
- # @private
220
- class BelongsToConfig
221
- include HashInitialization
222
- attr_reader :name, :owned, :fk, :fk_type, :child_classes
223
-
224
- def init_relational_association(child_configs, parent_config)
225
- @relational_association = RelationalAssociation.new(fk: fk, fk_type: fk_type, local_config: parent_config, remote_configs: child_configs)
186
+ def child_config
187
+ @relational_association.local_config
226
188
  end
227
189
 
228
- def get_child(parent)
229
- parent.send(name)
190
+ def foreign_key_info
191
+ @relational_association.foreign_key_info(@parent_config)
230
192
  end
193
+ end
194
+ # @private
195
+ class BelongsToConfig
196
+ include HashInitialization
197
+ attr_reader :name, :owned, :fk, :fk_type, :child_classes
231
198
 
232
- def set_child(parent, child)
233
- parent.send("#{name}=", child)
234
- end
199
+ def init_relational_association(child_configs, parent_config)
200
+ @relational_association = RelationalAssociation.new(fk: fk, fk_type: fk_type, local_config: parent_config, remote_configs: child_configs)
201
+ end
235
202
 
236
- def set_foreign_key(db_parent, child)
237
- @relational_association.set_foreign_key(db_parent, child)
238
- end
203
+ def get_child(parent)
204
+ parent.send(name)
205
+ end
206
+
207
+ def set_child(parent, child)
208
+ parent.send("#{name}=", child)
209
+ end
239
210
 
240
- def load_child(db_parent)
241
- @relational_association.load_remote(db_parent)
211
+ def set_foreign_key(db_parent, child)
212
+ @relational_association.set_foreign_key(db_parent, child)
213
+ end
214
+
215
+ def associated?(db_parent, db_child)
216
+ return false if child_config(db_parent).db_class != db_child.class
217
+ fk_value(db_parent) == db_child.id
218
+ end
219
+
220
+ def child_config(db_parent)
221
+ if @relational_association.polymorphic?
222
+ @relational_association.remote_config_for_local_db_object(db_parent)
223
+ else
224
+ @relational_association.remote_configs.first
225
+ end
226
+ end
227
+
228
+ def fk_value(db_parent)
229
+ db_parent.send(fk)
230
+ end
242
231
  end
243
- end
244
232
 
245
233
  # @private
246
234
  class HasOneConfig
@@ -248,6 +236,7 @@ class HasOneConfig
248
236
  attr_reader :name, :owned, :fk, :fk_type, :child_class
249
237
 
250
238
  def init_relational_association(child_config, parent_config)
239
+ @parent_config = parent_config
251
240
  @relational_association = RelationalAssociation.new(fk: fk, fk_type: fk_type, local_config: child_config, remote_configs: [parent_config])
252
241
  end
253
242
 
@@ -263,8 +252,18 @@ class HasOneConfig
263
252
  @relational_association.set_foreign_key(db_child, parent)
264
253
  end
265
254
 
266
- def load_child(db_parent)
267
- @relational_association.load_locals(db_parent).first
255
+ def associated?(db_parent, db_child)
256
+ return false if child_config.db_class != db_child.class
257
+ db_child.send(fk) == db_parent.id
258
+ end
259
+
260
+ def child_config
261
+ @relational_association.local_config
262
+ end
263
+
264
+ def foreign_key_info
265
+ @relational_association.foreign_key_info(@parent_config)
268
266
  end
269
267
  end
268
+
270
269
  end
@@ -0,0 +1,50 @@
1
+ module Vorpal
2
+ module DbDriver
3
+ extend self
4
+
5
+ def insert(config, db_objects)
6
+ if defined? ActiveRecord::Import
7
+ config.db_class.import db_objects
8
+ else
9
+ db_objects.each do |db_object|
10
+ db_object.save!
11
+ end
12
+ end
13
+ end
14
+
15
+ def update(config, db_objects)
16
+ db_objects.each do |db_object|
17
+ db_object.save!
18
+ end
19
+ end
20
+
21
+ def destroy(config, db_objects)
22
+ config.db_class.delete_all(id: db_objects.map(&:id))
23
+ end
24
+
25
+ def load_by_id(config, ids)
26
+ config.db_class.where(id: ids)
27
+ end
28
+
29
+ def load_by_foreign_key(config, id, foreign_key_info)
30
+ arel = config.db_class.where(foreign_key_info.fk_column => id)
31
+ arel = arel.where(foreign_key_info.fk_type_column => foreign_key_info.fk_type) if foreign_key_info.polymorphic?
32
+ arel.order(:id).all
33
+ end
34
+
35
+ def get_primary_keys(config, count)
36
+ result = execute("select nextval('#{sequence_name(config)}') from generate_series(1,#{count});")
37
+ result.column_values(0).map(&:to_i)
38
+ end
39
+
40
+ private
41
+
42
+ def execute(sql)
43
+ ActiveRecord::Base.connection.execute(sql)
44
+ end
45
+
46
+ def sequence_name(config)
47
+ "#{config.table_name}_id_seq"
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,131 @@
1
+ require 'vorpal/loaded_objects'
2
+ require 'vorpal/util/array_hash'
3
+ require 'vorpal/db_driver'
4
+
5
+ module Vorpal
6
+
7
+ # @private
8
+ class DbLoader
9
+ def initialize(configs, only_owned)
10
+ @configs = configs
11
+ @only_owned = only_owned
12
+ end
13
+
14
+ def load_from_db(ids, domain_class)
15
+ config = @configs.config_for(domain_class)
16
+ @loaded_objects = LoadedObjects.new
17
+ @lookup_instructions = LookupInstructions.new
18
+ @lookup_instructions.lookup_by_id(config, ids)
19
+
20
+ until @lookup_instructions.empty?
21
+ lookup = @lookup_instructions.next_lookup
22
+ new_objects = lookup.load_all
23
+ @loaded_objects.add(lookup.config, new_objects)
24
+ explore_objects(new_objects)
25
+ end
26
+
27
+ @loaded_objects
28
+ end
29
+
30
+ private
31
+
32
+ def explore_objects(objects_to_explore)
33
+ objects_to_explore.each do |db_object|
34
+ config = @configs.config_for_db(db_object.class)
35
+ config.has_manys.each do |has_many_config|
36
+ lookup_by_fk(db_object, has_many_config) if explore_association?(has_many_config)
37
+ end
38
+
39
+ config.has_ones.each do |has_one_config|
40
+ lookup_by_fk(db_object, has_one_config) if explore_association?(has_one_config)
41
+ end
42
+
43
+ config.belongs_tos.each do |belongs_to_config|
44
+ lookup_by_id(db_object, belongs_to_config) if explore_association?(belongs_to_config)
45
+ end
46
+ end
47
+ end
48
+
49
+ def explore_association?(association_config)
50
+ !@only_owned || association_config.owned == true
51
+ end
52
+
53
+ def lookup_by_id(db_object, belongs_to_config)
54
+ child_config = belongs_to_config.child_config(db_object)
55
+ id = belongs_to_config.fk_value(db_object)
56
+ return if @loaded_objects.id_lookup_done?(child_config, id)
57
+ @lookup_instructions.lookup_by_id(child_config, id)
58
+ end
59
+
60
+ def lookup_by_fk(db_object, has_many_config)
61
+ child_config = has_many_config.child_config
62
+ fk_info = has_many_config.foreign_key_info
63
+ fk_value = db_object.id
64
+ return if @loaded_objects.fk_lookup_done?(child_config, fk_info, fk_value)
65
+ @lookup_instructions.lookup_by_fk(child_config, fk_info, fk_value)
66
+ end
67
+ end
68
+
69
+ # @private
70
+ class LookupInstructions
71
+ include ArrayHash
72
+ def initialize
73
+ @lookup_by_id = {}
74
+ @lookup_by_fk = {}
75
+ end
76
+
77
+ def lookup_by_id(config, ids)
78
+ add_to_hash(@lookup_by_id, config, Array(ids))
79
+ end
80
+
81
+ def lookup_by_fk(config, fk_info, fk_value)
82
+ add_to_hash(@lookup_by_fk, [config, fk_info], fk_value)
83
+ end
84
+
85
+ def next_lookup
86
+ if @lookup_by_id.empty?
87
+ config, fk_info = @lookup_by_fk.first.first
88
+ fk_values = @lookup_by_fk.delete([config, fk_info])
89
+ LookupByFk.new(config, fk_info, fk_values)
90
+ else
91
+ config = @lookup_by_id.first.first
92
+ ids = @lookup_by_id.delete(config)
93
+ LookupById.new(config, ids)
94
+ end
95
+ end
96
+
97
+ def empty?
98
+ @lookup_by_id.empty? && @lookup_by_fk.empty?
99
+ end
100
+ end
101
+
102
+ # @private
103
+ class LookupById
104
+ attr_reader :config
105
+ def initialize(config, ids)
106
+ @config = config
107
+ @ids = ids
108
+ end
109
+
110
+ def load_all
111
+ return [] if @ids.empty?
112
+ DbDriver.load_by_id(@config, @ids)
113
+ end
114
+ end
115
+
116
+ # @private
117
+ class LookupByFk
118
+ attr_reader :config
119
+ def initialize(config, fk_info, fk_values)
120
+ @config = config
121
+ @fk_info = fk_info
122
+ @fk_values = fk_values
123
+ end
124
+
125
+ def load_all
126
+ return [] if @fk_values.empty?
127
+ DbDriver.load_by_foreign_key(@config, @fk_values, @fk_info)
128
+ end
129
+ end
130
+
131
+ end