vorpal 0.0.6.rc4 → 0.0.6
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 +8 -8
- data/README.md +10 -3
- data/lib/vorpal/aggregate_repository.rb +227 -190
- data/lib/vorpal/aggregate_traversal.rb +35 -35
- data/lib/vorpal/aggregate_utils.rb +1 -0
- data/lib/vorpal/config_builder.rb +120 -119
- data/lib/vorpal/configs.rb +22 -8
- data/lib/vorpal/configuration.rb +43 -40
- data/lib/vorpal/db_driver.rb +14 -2
- data/lib/vorpal/db_loader.rb +102 -104
- data/lib/vorpal/identity_map.rb +25 -25
- data/lib/vorpal/loaded_objects.rb +29 -28
- data/lib/vorpal/util/array_hash.rb +12 -14
- data/lib/vorpal/version.rb +1 -1
- data/spec/vorpal/{aggregate_repository_spec.rb → acceptance/aggregate_repository_spec.rb} +133 -2
- data/spec/vorpal/{performance_spec.rb → acceptance/performance_spec.rb} +4 -4
- data/spec/vorpal/{class_config_builder_spec.rb → unit/class_config_builder_spec.rb} +0 -0
- data/spec/vorpal/{configs_spec.rb → unit/configs_spec.rb} +0 -0
- data/spec/vorpal/{identity_map_spec.rb → unit/identity_map_spec.rb} +0 -0
- data/spec/vorpal/unit/loaded_objects_spec.rb +22 -0
- data/vorpal.gemspec +1 -0
- metadata +30 -14
data/lib/vorpal/configuration.rb
CHANGED
@@ -2,49 +2,52 @@ require 'vorpal/aggregate_repository'
|
|
2
2
|
require 'vorpal/config_builder'
|
3
3
|
|
4
4
|
module Vorpal
|
5
|
+
module Configuration
|
5
6
|
|
6
|
-
|
7
|
+
# Configures and creates a {Vorpal::AggregateRepository} instance.
|
8
|
+
#
|
9
|
+
# @param options [Hash] Global configuration options for the repository instance.
|
10
|
+
# @option options [Object] :db_driver (Object that will be used to interact with the DB.)
|
11
|
+
# Must be duck-type compatible with {Vorpal::DbDriver}.
|
12
|
+
#
|
13
|
+
# @return [Vorpal::AggregateRepository] Repository instance.
|
14
|
+
def define(options={}, &block)
|
15
|
+
master_config = build_config(&block)
|
16
|
+
db_driver = options.fetch(:db_driver, DbDriver.new)
|
17
|
+
AggregateRepository.new(db_driver, master_config)
|
18
|
+
end
|
7
19
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
# Class of the ActiveRecord object that will map this domain class to the DB.
|
27
|
-
# @option options [Object] :serializer (map the {ConfigBuilder#fields} directly)
|
28
|
-
# Object that will convert the domain objects into a hash.
|
29
|
-
#
|
30
|
-
# Must have a `(Hash) serialize(Object)` method.
|
31
|
-
# @option options [Object] :deserializer (map the {ConfigBuilder#fields} directly)
|
32
|
-
# Object that will set a hash of attribute_names->values onto a new domain
|
33
|
-
# object.
|
34
|
-
#
|
35
|
-
# Must have a `(Object) deserialize(Object, Hash)` method.
|
36
|
-
def map(domain_class, options={}, &block)
|
37
|
-
builder = ConfigBuilder.new(domain_class, options)
|
38
|
-
builder.instance_exec(&block) if block_given?
|
20
|
+
# Maps a domain class to a relational table.
|
21
|
+
#
|
22
|
+
# @param domain_class [Class] Type of the domain model to be mapped
|
23
|
+
# @param options [Hash] Configure how to map the domain model
|
24
|
+
# @option options [String] :to (Class with the same name as the domain class with a 'DB' appended.)
|
25
|
+
# Class of the ActiveRecord object that will map this domain class to the DB.
|
26
|
+
# @option options [Object] :serializer (map the {ConfigBuilder#fields} directly)
|
27
|
+
# Object that will convert the domain objects into a hash.
|
28
|
+
#
|
29
|
+
# Must have a `(Hash) serialize(Object)` method.
|
30
|
+
# @option options [Object] :deserializer (map the {ConfigBuilder#fields} directly)
|
31
|
+
# Object that will set a hash of attribute_names->values onto a new domain
|
32
|
+
# object.
|
33
|
+
#
|
34
|
+
# Must have a `(Object) deserialize(Object, Hash)` method.
|
35
|
+
def map(domain_class, options={}, &block)
|
36
|
+
@class_configs << build_class_config(domain_class, options, &block)
|
37
|
+
end
|
39
38
|
|
40
|
-
@
|
41
|
-
|
39
|
+
# @private
|
40
|
+
def build_class_config(domain_class, options={}, &block)
|
41
|
+
builder = ConfigBuilder.new(domain_class, options)
|
42
|
+
builder.instance_exec(&block) if block_given?
|
43
|
+
builder.build
|
44
|
+
end
|
42
45
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
46
|
+
# @private
|
47
|
+
def build_config(&block)
|
48
|
+
@class_configs = []
|
49
|
+
self.instance_exec(&block)
|
50
|
+
MasterConfig.new(@class_configs)
|
51
|
+
end
|
48
52
|
end
|
49
53
|
end
|
50
|
-
end
|
data/lib/vorpal/db_driver.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
module Vorpal
|
2
|
+
# Interfaces between the database and Vorpal
|
3
|
+
#
|
4
|
+
# Currently only works for PostgreSQL.
|
2
5
|
class DbDriver
|
3
6
|
def initialize
|
4
7
|
@sequence_names = {}
|
@@ -20,20 +23,29 @@ module Vorpal
|
|
20
23
|
end
|
21
24
|
end
|
22
25
|
|
23
|
-
def destroy(class_config,
|
24
|
-
class_config.db_class.delete_all(id:
|
26
|
+
def destroy(class_config, ids)
|
27
|
+
class_config.db_class.delete_all(id: ids)
|
25
28
|
end
|
26
29
|
|
30
|
+
# Loads instances of the given class by primary key.
|
31
|
+
#
|
32
|
+
# @return [[Object]] An array of entities.
|
27
33
|
def load_by_id(class_config, ids)
|
28
34
|
class_config.db_class.where(id: ids)
|
29
35
|
end
|
30
36
|
|
37
|
+
# Loads instances of the given class whose foreign key has the given value.
|
38
|
+
#
|
39
|
+
# @return [[Object]] An array of entities.
|
31
40
|
def load_by_foreign_key(class_config, id, foreign_key_info)
|
32
41
|
arel = class_config.db_class.where(foreign_key_info.fk_column => id)
|
33
42
|
arel = arel.where(foreign_key_info.fk_type_column => foreign_key_info.fk_type) if foreign_key_info.polymorphic?
|
34
43
|
arel.order(:id).all
|
35
44
|
end
|
36
45
|
|
46
|
+
# Fetches primary key values to be used for new entities.
|
47
|
+
#
|
48
|
+
# @return [[Integer]] An array of unused primary keys.
|
37
49
|
def get_primary_keys(class_config, count)
|
38
50
|
result = execute("select nextval($1) from generate_series(1,$2);", [sequence_name(class_config), count])
|
39
51
|
result.rows.map(&:first).map(&:to_i)
|
data/lib/vorpal/db_loader.rb
CHANGED
@@ -3,138 +3,136 @@ require 'vorpal/util/array_hash'
|
|
3
3
|
require 'vorpal/db_driver'
|
4
4
|
|
5
5
|
module Vorpal
|
6
|
+
# Handles loading of objects from the database.
|
7
|
+
#
|
8
|
+
# @private
|
9
|
+
class DbLoader
|
10
|
+
def initialize(only_owned, db_driver)
|
11
|
+
@only_owned = only_owned
|
12
|
+
@db_driver = db_driver
|
13
|
+
end
|
6
14
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def initialize(only_owned, db_driver)
|
12
|
-
@only_owned = only_owned
|
13
|
-
@db_driver = db_driver
|
14
|
-
end
|
15
|
+
def load_from_db(ids, config)
|
16
|
+
@loaded_objects = LoadedObjects.new
|
17
|
+
@lookup_instructions = LookupInstructions.new
|
18
|
+
@lookup_instructions.lookup_by_id(config, ids)
|
15
19
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
+
until @lookup_instructions.empty?
|
21
|
+
lookup = @lookup_instructions.next_lookup
|
22
|
+
new_objects = lookup.load_all(@db_driver)
|
23
|
+
@loaded_objects.add(lookup.config, new_objects)
|
24
|
+
explore_objects(lookup.config, new_objects)
|
25
|
+
end
|
20
26
|
|
21
|
-
|
22
|
-
lookup = @lookup_instructions.next_lookup
|
23
|
-
new_objects = lookup.load_all(@db_driver)
|
24
|
-
@loaded_objects.add(lookup.config, new_objects)
|
25
|
-
explore_objects(lookup.config, new_objects)
|
27
|
+
@loaded_objects
|
26
28
|
end
|
27
29
|
|
28
|
-
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
30
|
+
private
|
32
31
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
32
|
+
def explore_objects(config, objects_to_explore)
|
33
|
+
objects_to_explore.each do |db_object|
|
34
|
+
config.has_manys.each do |has_many_config|
|
35
|
+
lookup_by_fk(db_object, has_many_config) if explore_association?(has_many_config)
|
36
|
+
end
|
38
37
|
|
39
|
-
|
40
|
-
|
41
|
-
|
38
|
+
config.has_ones.each do |has_one_config|
|
39
|
+
lookup_by_fk(db_object, has_one_config) if explore_association?(has_one_config)
|
40
|
+
end
|
42
41
|
|
43
|
-
|
44
|
-
|
42
|
+
config.belongs_tos.each do |belongs_to_config|
|
43
|
+
lookup_by_id(db_object, belongs_to_config) if explore_association?(belongs_to_config)
|
44
|
+
end
|
45
45
|
end
|
46
46
|
end
|
47
|
-
end
|
48
47
|
|
49
|
-
|
50
|
-
|
51
|
-
|
48
|
+
def explore_association?(association_config)
|
49
|
+
!@only_owned || association_config.owned == true
|
50
|
+
end
|
52
51
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
52
|
+
def lookup_by_id(db_object, belongs_to_config)
|
53
|
+
child_config = belongs_to_config.child_config(db_object)
|
54
|
+
id = belongs_to_config.fk_value(db_object)
|
55
|
+
return if id.nil? || @loaded_objects.already_loaded?(child_config, id)
|
56
|
+
@lookup_instructions.lookup_by_id(child_config, id)
|
57
|
+
end
|
59
58
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
59
|
+
def lookup_by_fk(db_object, has_some_config)
|
60
|
+
child_config = has_some_config.child_config
|
61
|
+
fk_info = has_some_config.foreign_key_info
|
62
|
+
fk_value = db_object.id
|
63
|
+
@lookup_instructions.lookup_by_fk(child_config, fk_info, fk_value)
|
64
|
+
end
|
65
65
|
end
|
66
|
-
end
|
67
66
|
|
68
|
-
# @private
|
69
|
-
class LookupInstructions
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
67
|
+
# @private
|
68
|
+
class LookupInstructions
|
69
|
+
include ArrayHash
|
70
|
+
def initialize
|
71
|
+
@lookup_by_id = {}
|
72
|
+
@lookup_by_fk = {}
|
73
|
+
end
|
75
74
|
|
76
|
-
|
77
|
-
|
78
|
-
|
75
|
+
def lookup_by_id(config, ids)
|
76
|
+
add_to_hash(@lookup_by_id, config, Array(ids))
|
77
|
+
end
|
79
78
|
|
80
|
-
|
81
|
-
|
82
|
-
|
79
|
+
def lookup_by_fk(config, fk_info, fk_value)
|
80
|
+
add_to_hash(@lookup_by_fk, [config, fk_info], fk_value)
|
81
|
+
end
|
83
82
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
83
|
+
def next_lookup
|
84
|
+
if @lookup_by_id.empty?
|
85
|
+
pop_fk_lookup
|
86
|
+
else
|
87
|
+
pop_id_lookup
|
88
|
+
end
|
89
89
|
end
|
90
|
-
end
|
91
90
|
|
92
|
-
|
93
|
-
|
94
|
-
|
91
|
+
def empty?
|
92
|
+
@lookup_by_id.empty? && @lookup_by_fk.empty?
|
93
|
+
end
|
95
94
|
|
96
|
-
|
95
|
+
private
|
97
96
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
97
|
+
def pop_id_lookup
|
98
|
+
config, ids = pop(@lookup_by_id)
|
99
|
+
LookupById.new(config, ids)
|
100
|
+
end
|
102
101
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
102
|
+
def pop_fk_lookup
|
103
|
+
key, fk_values = pop(@lookup_by_fk)
|
104
|
+
config = key.first
|
105
|
+
fk_info = key.last
|
106
|
+
LookupByFk.new(config, fk_info, fk_values)
|
107
|
+
end
|
108
108
|
end
|
109
|
-
end
|
110
109
|
|
111
|
-
# @private
|
112
|
-
class LookupById
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
110
|
+
# @private
|
111
|
+
class LookupById
|
112
|
+
attr_reader :config
|
113
|
+
def initialize(config, ids)
|
114
|
+
@config = config
|
115
|
+
@ids = ids
|
116
|
+
end
|
118
117
|
|
119
|
-
|
120
|
-
|
121
|
-
|
118
|
+
def load_all(db_driver)
|
119
|
+
return [] if @ids.empty?
|
120
|
+
db_driver.load_by_id(@config, @ids)
|
121
|
+
end
|
122
122
|
end
|
123
|
-
end
|
124
123
|
|
125
|
-
# @private
|
126
|
-
class LookupByFk
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
124
|
+
# @private
|
125
|
+
class LookupByFk
|
126
|
+
attr_reader :config
|
127
|
+
def initialize(config, fk_info, fk_values)
|
128
|
+
@config = config
|
129
|
+
@fk_info = fk_info
|
130
|
+
@fk_values = fk_values
|
131
|
+
end
|
133
132
|
|
134
|
-
|
135
|
-
|
136
|
-
|
133
|
+
def load_all(db_driver)
|
134
|
+
return [] if @fk_values.empty?
|
135
|
+
db_driver.load_by_foreign_key(@config, @fk_values, @fk_info)
|
136
|
+
end
|
137
137
|
end
|
138
138
|
end
|
139
|
-
|
140
|
-
end
|
data/lib/vorpal/identity_map.rb
CHANGED
@@ -1,34 +1,34 @@
|
|
1
1
|
module Vorpal
|
2
|
-
class IdentityMap
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
class IdentityMap
|
3
|
+
def initialize
|
4
|
+
@entities = {}
|
5
|
+
end
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
def get(key_object)
|
8
|
+
@entities[key(key_object)]
|
9
|
+
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
def set(key_object, object)
|
12
|
+
@entities[key(key_object)] = object
|
13
|
+
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
15
|
+
def get_and_set(key_object)
|
16
|
+
object = get(key_object)
|
17
|
+
object = yield if object.nil?
|
18
|
+
set(key_object, object)
|
19
|
+
object
|
20
|
+
end
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
def map(key_objects)
|
23
|
+
key_objects.map { |k| @entities[key(k)] }
|
24
|
+
end
|
25
25
|
|
26
|
-
|
26
|
+
private
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
def key(key_object)
|
29
|
+
return nil unless key_object
|
30
|
+
raise "Cannot put entity '#{key_object.inspect}' into IdentityMap without an id." if key_object.id.nil?
|
31
|
+
[key_object.id, key_object.class.name]
|
32
|
+
end
|
32
33
|
end
|
33
34
|
end
|
34
|
-
end
|
@@ -3,38 +3,39 @@ require 'forwardable'
|
|
3
3
|
|
4
4
|
module Vorpal
|
5
5
|
|
6
|
-
# @private
|
7
|
-
class LoadedObjects
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
def add(config, objects)
|
21
|
-
add_to_hash(@objects, config, objects)
|
6
|
+
# @private
|
7
|
+
class LoadedObjects
|
8
|
+
include ArrayHash
|
9
|
+
extend Forwardable
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
attr_reader :objects
|
13
|
+
def_delegators :objects, :each
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@objects = Hash.new([])
|
17
|
+
@objects_by_id = Hash.new
|
18
|
+
end
|
22
19
|
|
23
|
-
|
24
|
-
|
20
|
+
def add(config, objects)
|
21
|
+
objects_to_add = objects.map do |object|
|
22
|
+
if !already_loaded?(config, object.id)
|
23
|
+
@objects_by_id[[config.domain_class.name, object.id]] = object
|
24
|
+
end
|
25
|
+
end
|
26
|
+
add_to_hash(@objects, config, objects_to_add.compact)
|
25
27
|
end
|
26
|
-
end
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
def find_by_id(config, id)
|
30
|
+
@objects_by_id[[config.domain_class.name, id]]
|
31
|
+
end
|
31
32
|
|
32
|
-
|
33
|
-
|
34
|
-
|
33
|
+
def all_objects
|
34
|
+
@objects_by_id.values
|
35
|
+
end
|
35
36
|
|
36
|
-
|
37
|
-
|
37
|
+
def already_loaded?(config, id)
|
38
|
+
!find_by_id(config, id).nil?
|
39
|
+
end
|
38
40
|
end
|
39
41
|
end
|
40
|
-
end
|