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