vorpal 0.1.0.rc3 → 1.0.0
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 +5 -5
- data/README.md +0 -2
- data/lib/vorpal/db_loader.rb +3 -4
- data/lib/vorpal/driver/postgresql.rb +145 -0
- data/lib/vorpal/dsl/configuration.rb +5 -4
- data/lib/vorpal/engine.rb +7 -8
- data/lib/vorpal/version.rb +1 -1
- data/spec/vorpal/acceptance/aggregate_mapper_spec.rb +1 -1
- data/spec/vorpal/integration/{db_driver_spec.rb → driver/postgresql_spec.rb} +8 -8
- data/spec/vorpal/unit/db_loader_spec.rb +5 -5
- data/spec/vorpal/unit/dsl/config_builder_spec.rb +0 -1
- data/spec/vorpal/unit/dsl/defaults_generator_spec.rb +2 -2
- data/vorpal.gemspec +2 -0
- metadata +9 -10
- data/.ruby-version +0 -1
- data/lib/vorpal/db_driver.rb +0 -143
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2fdca5b34f5a1ac2b9b228849c53b8322e542ab9bc75a5c15a0c8003f1f1fd94
|
4
|
+
data.tar.gz: aebdb05efdfd853644afa49bf101d845082a7d627e47c50bf609830be46901f0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1416691727efd279769865a4725949ff41965d120d6431a3737880936838e6760d857c3a5546b5b73774a1ea24cb1ef742d6f7cce6dfa0497de34739ba67b22a
|
7
|
+
data.tar.gz: 117da60ac019fa9b373d7f45ad6893b24de5611533d348034ef4f4d978adc81468d3228d55adf8107482084b219c47509cb8fef69516ae0ccdea31f73e3a0390
|
data/README.md
CHANGED
@@ -45,8 +45,6 @@ Or install it yourself as:
|
|
45
45
|
|
46
46
|
## Usage
|
47
47
|
|
48
|
-
**Warning! API still in flux! Expect it to change with every release until 0.1.0. After this point, semantic versioning will be used.**
|
49
|
-
|
50
48
|
Start with a domain model of POROs and AR::Base objects that form an aggregate:
|
51
49
|
|
52
50
|
```ruby
|
data/lib/vorpal/db_loader.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'vorpal/loaded_objects'
|
2
2
|
require 'vorpal/util/array_hash'
|
3
|
-
require 'vorpal/db_driver'
|
4
3
|
|
5
4
|
module Vorpal
|
6
5
|
# Handles loading of objects from the database.
|
@@ -13,7 +12,7 @@ module Vorpal
|
|
13
12
|
end
|
14
13
|
|
15
14
|
def load_from_db(ids, config)
|
16
|
-
db_roots = @db_driver.load_by_id(config, ids)
|
15
|
+
db_roots = @db_driver.load_by_id(config.db_class, ids)
|
17
16
|
load_from_db_objects(db_roots, config)
|
18
17
|
end
|
19
18
|
|
@@ -123,7 +122,7 @@ module Vorpal
|
|
123
122
|
|
124
123
|
def load_all(db_driver)
|
125
124
|
return [] if @ids.empty?
|
126
|
-
db_driver.load_by_id(@config, @ids)
|
125
|
+
db_driver.load_by_id(@config.db_class, @ids)
|
127
126
|
end
|
128
127
|
end
|
129
128
|
|
@@ -138,7 +137,7 @@ module Vorpal
|
|
138
137
|
|
139
138
|
def load_all(db_driver)
|
140
139
|
return [] if @fk_values.empty?
|
141
|
-
db_driver.load_by_foreign_key(@config, @fk_values, @fk_info)
|
140
|
+
db_driver.load_by_foreign_key(@config.db_class, @fk_values, @fk_info)
|
142
141
|
end
|
143
142
|
end
|
144
143
|
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'vorpal/util/string_utils.rb'
|
2
|
+
|
3
|
+
module Vorpal
|
4
|
+
module Driver
|
5
|
+
# Interface between the database and Vorpal for PostgreSQL using ActiveRecord.
|
6
|
+
class Postgresql
|
7
|
+
def initialize
|
8
|
+
@sequence_names = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def insert(db_class, db_objects)
|
12
|
+
if defined? ActiveRecord::Import
|
13
|
+
db_class.import(db_objects, validate: false)
|
14
|
+
else
|
15
|
+
db_objects.each do |db_object|
|
16
|
+
db_object.save!(validate: false)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def update(db_class, db_objects)
|
22
|
+
db_objects.each do |db_object|
|
23
|
+
db_object.save!(validate: false)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def destroy(db_class, ids)
|
28
|
+
db_class.delete_all(id: ids)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Loads instances of the given class by primary key.
|
32
|
+
#
|
33
|
+
# @param db_class [Class] A subclass of ActiveRecord::Base
|
34
|
+
# @return [[Object]] An array of entities.
|
35
|
+
def load_by_id(db_class, ids)
|
36
|
+
db_class.where(id: ids).to_a
|
37
|
+
end
|
38
|
+
|
39
|
+
# Loads instances of the given class whose foreign key has the given value.
|
40
|
+
#
|
41
|
+
# @param db_class [Class] A subclass of ActiveRecord::Base
|
42
|
+
# @param id [Integer] The value of the foreign key to find by. (Can also be an array of ids.)
|
43
|
+
# @param foreign_key_info [ForeignKeyInfo] Meta data for the foreign key.
|
44
|
+
# @return [[Object]] An array of entities.
|
45
|
+
def load_by_foreign_key(db_class, id, foreign_key_info)
|
46
|
+
arel = db_class.where(foreign_key_info.fk_column => id)
|
47
|
+
arel = arel.where(foreign_key_info.fk_type_column => foreign_key_info.fk_type) if foreign_key_info.polymorphic?
|
48
|
+
arel.to_a
|
49
|
+
end
|
50
|
+
|
51
|
+
# Fetches primary key values to be used for new entities.
|
52
|
+
#
|
53
|
+
# @param db_class [Class] A subclass of ActiveRecord::Base
|
54
|
+
# @return [[Integer]] An array of unused primary keys.
|
55
|
+
def get_primary_keys(db_class, count)
|
56
|
+
result = execute("select nextval($1) from generate_series(1,$2);", [sequence_name(db_class), count])
|
57
|
+
result.rows.map(&:first).map(&:to_i)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Builds an ORM Class for accessing data in the given DB table.
|
61
|
+
#
|
62
|
+
# @param model_class [Class] The PORO class that we are creating a DB interface class for.
|
63
|
+
# @param table_name [String] Name of the DB table the DB class should interface with.
|
64
|
+
# @return [Class] ActiveRecord::Base Class
|
65
|
+
def build_db_class(model_class, table_name)
|
66
|
+
db_class = Class.new(ActiveRecord::Base) do
|
67
|
+
class << self
|
68
|
+
# This is overridden for two reasons:
|
69
|
+
# 1) For anonymous classes, #name normally returns nil. Class names in Ruby come from the
|
70
|
+
# name of the constant they are assigned to.
|
71
|
+
# 2) Because the default implementation for Class#name for anonymous classes is very, very
|
72
|
+
# slow. https://bugs.ruby-lang.org/issues/11119
|
73
|
+
# Remove this override once #2 has been fixed!
|
74
|
+
def name
|
75
|
+
@name ||= "Vorpal_generated_ActiveRecord__Base_class_for_#{vorpal_model_class_name}"
|
76
|
+
end
|
77
|
+
|
78
|
+
# Overridden because, like #name, the default implementation for anonymous classes is very,
|
79
|
+
# very slow.
|
80
|
+
def to_s
|
81
|
+
name
|
82
|
+
end
|
83
|
+
|
84
|
+
attr_accessor :vorpal_model_class_name
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
db_class.vorpal_model_class_name = Util::StringUtils.escape_class_name(model_class.name)
|
89
|
+
db_class.table_name = table_name
|
90
|
+
db_class
|
91
|
+
end
|
92
|
+
|
93
|
+
# Builds a composable query object (e.g. ActiveRecord::Relation) with Vorpal methods mixed in
|
94
|
+
# for querying for instances of the given AR::Base class.
|
95
|
+
#
|
96
|
+
# @param db_class [Class] A subclass of ActiveRecord::Base
|
97
|
+
def query(db_class, aggregate_mapper)
|
98
|
+
db_class.unscoped.extending(ArelQueryMethods.new(aggregate_mapper))
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def sequence_name(db_class)
|
104
|
+
@sequence_names[db_class] ||= execute(
|
105
|
+
"SELECT substring(column_default from '''(.*)''') FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1 AND column_name = 'id' LIMIT 1",
|
106
|
+
[db_class.table_name]
|
107
|
+
).rows.first.first
|
108
|
+
end
|
109
|
+
|
110
|
+
def execute(sql, binds)
|
111
|
+
binds = binds.map { |row| [nil, row] }
|
112
|
+
ActiveRecord::Base.connection.exec_query(sql, 'SQL', binds)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class ArelQueryMethods < Module
|
117
|
+
def initialize(mapper)
|
118
|
+
@mapper = mapper
|
119
|
+
end
|
120
|
+
|
121
|
+
def extended(descendant)
|
122
|
+
super
|
123
|
+
descendant.extend(Methods)
|
124
|
+
descendant.vorpal_aggregate_mapper = @mapper
|
125
|
+
end
|
126
|
+
|
127
|
+
# Methods in this module will appear on any composable
|
128
|
+
module Methods
|
129
|
+
attr_writer :vorpal_aggregate_mapper
|
130
|
+
|
131
|
+
# See {AggregateMapper#load_many}.
|
132
|
+
def load_many
|
133
|
+
db_roots = self.to_a
|
134
|
+
@vorpal_aggregate_mapper.load_many(db_roots)
|
135
|
+
end
|
136
|
+
|
137
|
+
# See {AggregateMapper#load_one}.
|
138
|
+
def load_one
|
139
|
+
db_root = self.first
|
140
|
+
@vorpal_aggregate_mapper.load_one(db_root)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'vorpal/engine'
|
2
2
|
require 'vorpal/dsl/config_builder'
|
3
|
+
require 'vorpal/driver/postgresql'
|
3
4
|
|
4
5
|
module Vorpal
|
5
6
|
module Dsl
|
@@ -9,12 +10,12 @@ module Vorpal
|
|
9
10
|
#
|
10
11
|
# @param options [Hash] Global configuration options for the engine instance.
|
11
12
|
# @option options [Object] :db_driver (Object that will be used to interact with the DB.)
|
12
|
-
# Must be duck-type compatible with {
|
13
|
+
# Must be duck-type compatible with {Postgresql}.
|
13
14
|
#
|
14
15
|
# @return [Engine] Instance of the mapping engine.
|
15
16
|
def define(options={}, &block)
|
16
17
|
master_config = build_config(&block)
|
17
|
-
db_driver = options.fetch(:db_driver,
|
18
|
+
db_driver = options.fetch(:db_driver, Driver::Postgresql.new)
|
18
19
|
Engine.new(db_driver, master_config)
|
19
20
|
end
|
20
21
|
|
@@ -40,7 +41,7 @@ module Vorpal
|
|
40
41
|
|
41
42
|
# @private
|
42
43
|
def build_class_config(domain_class, options={}, &block)
|
43
|
-
builder = ConfigBuilder.new(domain_class, options,
|
44
|
+
builder = ConfigBuilder.new(domain_class, options, Driver::Postgresql.new)
|
44
45
|
builder.instance_exec(&block) if block_given?
|
45
46
|
builder.build
|
46
47
|
end
|
@@ -53,4 +54,4 @@ module Vorpal
|
|
53
54
|
end
|
54
55
|
end
|
55
56
|
end
|
56
|
-
end
|
57
|
+
end
|
data/lib/vorpal/engine.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'vorpal/identity_map'
|
2
2
|
require 'vorpal/aggregate_utils'
|
3
3
|
require 'vorpal/db_loader'
|
4
|
-
require 'vorpal/db_driver'
|
5
4
|
require 'vorpal/aggregate_mapper'
|
6
5
|
require 'vorpal/exceptions'
|
7
6
|
|
@@ -80,7 +79,7 @@ module Vorpal
|
|
80
79
|
|
81
80
|
loaded_db_objects = load_owned_from_db(ids, domain_class)
|
82
81
|
loaded_db_objects.each do |config, db_objects|
|
83
|
-
@db_driver.destroy(config, db_objects.map(&:id))
|
82
|
+
@db_driver.destroy(config.db_class, db_objects.map(&:id))
|
84
83
|
end
|
85
84
|
ids
|
86
85
|
end
|
@@ -91,7 +90,7 @@ module Vorpal
|
|
91
90
|
end
|
92
91
|
|
93
92
|
def query(domain_class)
|
94
|
-
@db_driver.query(@configs.config_for(domain_class), mapper_for(domain_class))
|
93
|
+
@db_driver.query(@configs.config_for(domain_class).db_class, mapper_for(domain_class))
|
95
94
|
end
|
96
95
|
|
97
96
|
private
|
@@ -166,7 +165,7 @@ module Vorpal
|
|
166
165
|
def set_primary_keys(owned_objects, mapping)
|
167
166
|
owned_objects.each do |config, objects|
|
168
167
|
in_need_of_primary_keys = objects.find_all { |obj| obj.id.nil? }
|
169
|
-
primary_keys = @db_driver.get_primary_keys(config, in_need_of_primary_keys.length)
|
168
|
+
primary_keys = @db_driver.get_primary_keys(config.db_class, in_need_of_primary_keys.length)
|
170
169
|
in_need_of_primary_keys.zip(primary_keys).each do |object, primary_key|
|
171
170
|
mapping[object].id = primary_key
|
172
171
|
object.id = primary_key
|
@@ -207,11 +206,11 @@ module Vorpal
|
|
207
206
|
owned_objects.each do |config, objects|
|
208
207
|
objects_to_insert = grouped_new_objects[config] || []
|
209
208
|
db_objects_to_insert = objects_to_insert.map { |obj| mapping[obj] }
|
210
|
-
@db_driver.insert(config, db_objects_to_insert)
|
209
|
+
@db_driver.insert(config.db_class, db_objects_to_insert)
|
211
210
|
|
212
211
|
objects_to_update = objects - objects_to_insert
|
213
212
|
db_objects_to_update = objects_to_update.map { |obj| mapping[obj] }
|
214
|
-
@db_driver.update(config, db_objects_to_update)
|
213
|
+
@db_driver.update(config.db_class, db_objects_to_update)
|
215
214
|
end
|
216
215
|
end
|
217
216
|
|
@@ -221,7 +220,7 @@ module Vorpal
|
|
221
220
|
all_orphans = db_objects_in_db - db_objects_in_aggregate
|
222
221
|
grouped_orphans = all_orphans.group_by { |o| @configs.config_for_db_object(o) }
|
223
222
|
grouped_orphans.each do |config, orphans|
|
224
|
-
@db_driver.destroy(config, orphans)
|
223
|
+
@db_driver.destroy(config.db_class, orphans)
|
225
224
|
end
|
226
225
|
end
|
227
226
|
|
@@ -234,4 +233,4 @@ module Vorpal
|
|
234
233
|
objects.each { |object| object.id = nil }
|
235
234
|
end
|
236
235
|
end
|
237
|
-
end
|
236
|
+
end
|
data/lib/vorpal/version.rb
CHANGED
@@ -95,7 +95,7 @@ describe 'AggregateMapper' do
|
|
95
95
|
|
96
96
|
describe 'on error' do
|
97
97
|
it 'nils ids of new objects' do
|
98
|
-
db_driver = Vorpal::
|
98
|
+
db_driver = Vorpal::Driver::Postgresql.new
|
99
99
|
test_mapper = configure(db_driver: db_driver)
|
100
100
|
|
101
101
|
tree_db = db_class_for(Tree, test_mapper).create!
|
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'integration_spec_helper'
|
2
2
|
require 'vorpal'
|
3
3
|
|
4
|
-
describe Vorpal::
|
4
|
+
describe Vorpal::Driver::Postgresql do
|
5
5
|
describe '#build_db_class' do
|
6
|
-
let(:db_class) { subject.build_db_class(
|
6
|
+
let(:db_class) { subject.build_db_class(PostgresDriverSpec::Foo, 'example') }
|
7
7
|
|
8
8
|
it 'generates a valid class name so that rails auto-reloading works' do
|
9
9
|
expect { Vorpal.const_defined?(db_class.name) }.to_not raise_error
|
@@ -14,29 +14,29 @@ describe Vorpal::DbDriver do
|
|
14
14
|
end
|
15
15
|
|
16
16
|
it 'isolates two POROs that map to the same db table' do
|
17
|
-
db_class1 = build_db_class(
|
18
|
-
db_class2 = build_db_class(
|
17
|
+
db_class1 = build_db_class(PostgresDriverSpec::Foo)
|
18
|
+
db_class2 = build_db_class(PostgresDriverSpec::Bar)
|
19
19
|
|
20
20
|
expect(db_class1).to_not eq(db_class2)
|
21
21
|
expect(db_class1.name).to_not eq(db_class2.name)
|
22
22
|
end
|
23
23
|
|
24
24
|
it 'uses the model class name to make the generated AR::Base class name unique' do
|
25
|
-
db_class = build_db_class(
|
25
|
+
db_class = build_db_class(PostgresDriverSpec::Foo)
|
26
26
|
|
27
|
-
expect(db_class.name).to match("
|
27
|
+
expect(db_class.name).to match("PostgresDriverSpec__Foo")
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
31
|
private
|
32
32
|
|
33
|
-
module
|
33
|
+
module PostgresDriverSpec
|
34
34
|
class Foo; end
|
35
35
|
class Bar; end
|
36
36
|
end
|
37
37
|
|
38
38
|
def build_db_class(clazz)
|
39
|
-
db_driver = Vorpal::
|
39
|
+
db_driver = Vorpal::Driver::Postgresql.new
|
40
40
|
db_driver.build_db_class(clazz, 'example')
|
41
41
|
end
|
42
42
|
end
|
@@ -54,7 +54,7 @@ describe Vorpal::DbLoader do
|
|
54
54
|
#
|
55
55
|
# master_config = Vorpal::MasterConfig.new([post_config, comment_config])
|
56
56
|
#
|
57
|
-
# driver = Vorpal::
|
57
|
+
# driver = Vorpal::Postgresql.new
|
58
58
|
#
|
59
59
|
# best_comment_db = CommentDB.create!
|
60
60
|
# post_db = PostDB.create!(best_comment_id: best_comment_db.id)
|
@@ -90,9 +90,9 @@ describe Vorpal::DbLoader do
|
|
90
90
|
post_db.id = 100
|
91
91
|
best_comment_db.post_id = post_db.id
|
92
92
|
|
93
|
-
driver = instance_double("Vorpal::
|
94
|
-
expect(driver).to receive(:load_by_id).with(
|
95
|
-
expect(driver).to receive(:load_by_id).with(
|
93
|
+
driver = instance_double("Vorpal::Driver::Postgresql")
|
94
|
+
expect(driver).to receive(:load_by_id).with(PostDB, [post_db.id]).and_return([post_db])
|
95
|
+
expect(driver).to receive(:load_by_id).with(CommentDB, [best_comment_db.id]).and_return([best_comment_db])
|
96
96
|
expect(driver).to receive(:load_by_foreign_key).and_return([best_comment_db])
|
97
97
|
|
98
98
|
loader = Vorpal::DbLoader.new(false, driver)
|
@@ -100,4 +100,4 @@ describe Vorpal::DbLoader do
|
|
100
100
|
|
101
101
|
expect(loaded_objects.all_objects).to contain_exactly(post_db, best_comment_db)
|
102
102
|
end
|
103
|
-
end
|
103
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'unit_spec_helper'
|
2
2
|
|
3
3
|
require 'vorpal/dsl/defaults_generator'
|
4
|
-
require 'vorpal/
|
4
|
+
require 'vorpal/driver/postgresql'
|
5
5
|
|
6
6
|
describe Vorpal::Dsl::DefaultsGenerator do
|
7
7
|
class Tester; end
|
@@ -11,7 +11,7 @@ describe Vorpal::Dsl::DefaultsGenerator do
|
|
11
11
|
class Author; end
|
12
12
|
end
|
13
13
|
|
14
|
-
let(:db_driver) { instance_double(Vorpal::
|
14
|
+
let(:db_driver) { instance_double(Vorpal::Driver::Postgresql)}
|
15
15
|
|
16
16
|
describe '#build_db_class' do
|
17
17
|
it 'derives the table_name from the domain class name' do
|
data/vorpal.gemspec
CHANGED
@@ -18,6 +18,8 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
+
spec.required_ruby_version = ">= 2.1.6"
|
22
|
+
|
21
23
|
spec.add_runtime_dependency "simple_serializer", "~> 1.0"
|
22
24
|
spec.add_runtime_dependency "equalizer"
|
23
25
|
spec.add_runtime_dependency "activesupport"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vorpal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sean Kirby
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-06-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: simple_serializer
|
@@ -147,7 +147,6 @@ files:
|
|
147
147
|
- ".editorconfig"
|
148
148
|
- ".gitignore"
|
149
149
|
- ".rspec"
|
150
|
-
- ".ruby-version"
|
151
150
|
- ".yardopts"
|
152
151
|
- CHANGELOG.md
|
153
152
|
- Gemfile
|
@@ -159,8 +158,8 @@ files:
|
|
159
158
|
- lib/vorpal/aggregate_traversal.rb
|
160
159
|
- lib/vorpal/aggregate_utils.rb
|
161
160
|
- lib/vorpal/configs.rb
|
162
|
-
- lib/vorpal/db_driver.rb
|
163
161
|
- lib/vorpal/db_loader.rb
|
162
|
+
- lib/vorpal/driver/postgresql.rb
|
164
163
|
- lib/vorpal/dsl/config_builder.rb
|
165
164
|
- lib/vorpal/dsl/configuration.rb
|
166
165
|
- lib/vorpal/dsl/defaults_generator.rb
|
@@ -177,7 +176,7 @@ files:
|
|
177
176
|
- spec/integration_spec_helper.rb
|
178
177
|
- spec/unit_spec_helper.rb
|
179
178
|
- spec/vorpal/acceptance/aggregate_mapper_spec.rb
|
180
|
-
- spec/vorpal/integration/
|
179
|
+
- spec/vorpal/integration/driver/postgresql_spec.rb
|
181
180
|
- spec/vorpal/performance/performance_spec.rb
|
182
181
|
- spec/vorpal/unit/configs_spec.rb
|
183
182
|
- spec/vorpal/unit/db_loader_spec.rb
|
@@ -199,15 +198,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
199
198
|
requirements:
|
200
199
|
- - ">="
|
201
200
|
- !ruby/object:Gem::Version
|
202
|
-
version:
|
201
|
+
version: 2.1.6
|
203
202
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
204
203
|
requirements:
|
205
|
-
- - "
|
204
|
+
- - ">="
|
206
205
|
- !ruby/object:Gem::Version
|
207
|
-
version:
|
206
|
+
version: '0'
|
208
207
|
requirements: []
|
209
208
|
rubyforge_project:
|
210
|
-
rubygems_version: 2.
|
209
|
+
rubygems_version: 2.7.6
|
211
210
|
signing_key:
|
212
211
|
specification_version: 4
|
213
212
|
summary: Separate your domain model from your persistence mechanism.
|
@@ -217,7 +216,7 @@ test_files:
|
|
217
216
|
- spec/integration_spec_helper.rb
|
218
217
|
- spec/unit_spec_helper.rb
|
219
218
|
- spec/vorpal/acceptance/aggregate_mapper_spec.rb
|
220
|
-
- spec/vorpal/integration/
|
219
|
+
- spec/vorpal/integration/driver/postgresql_spec.rb
|
221
220
|
- spec/vorpal/performance/performance_spec.rb
|
222
221
|
- spec/vorpal/unit/configs_spec.rb
|
223
222
|
- spec/vorpal/unit/db_loader_spec.rb
|
data/.ruby-version
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
2.1.6
|
data/lib/vorpal/db_driver.rb
DELETED
@@ -1,143 +0,0 @@
|
|
1
|
-
require 'vorpal/util/string_utils.rb'
|
2
|
-
|
3
|
-
module Vorpal
|
4
|
-
# Interface between the database and Vorpal
|
5
|
-
#
|
6
|
-
# Currently only works for PostgreSQL via ActiveRecord.
|
7
|
-
class DbDriver
|
8
|
-
def initialize
|
9
|
-
@sequence_names = {}
|
10
|
-
end
|
11
|
-
|
12
|
-
def insert(class_config, db_objects)
|
13
|
-
if defined? ActiveRecord::Import
|
14
|
-
class_config.db_class.import(db_objects, validate: false)
|
15
|
-
else
|
16
|
-
db_objects.each do |db_object|
|
17
|
-
db_object.save!(validate: false)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def update(class_config, db_objects)
|
23
|
-
db_objects.each do |db_object|
|
24
|
-
db_object.save!(validate: false)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def destroy(class_config, ids)
|
29
|
-
class_config.db_class.delete_all(id: ids)
|
30
|
-
end
|
31
|
-
|
32
|
-
# Loads instances of the given class by primary key.
|
33
|
-
#
|
34
|
-
# @param class_config [ClassConfig]
|
35
|
-
# @return [[Object]] An array of entities.
|
36
|
-
def load_by_id(class_config, ids)
|
37
|
-
class_config.db_class.where(id: ids).to_a
|
38
|
-
end
|
39
|
-
|
40
|
-
# Loads instances of the given class whose foreign key has the given value.
|
41
|
-
#
|
42
|
-
# @param class_config [ClassConfig]
|
43
|
-
# @param foreign_key_info [ForeignKeyInfo]
|
44
|
-
# @return [[Object]] An array of entities.
|
45
|
-
def load_by_foreign_key(class_config, id, foreign_key_info)
|
46
|
-
arel = class_config.db_class.where(foreign_key_info.fk_column => id)
|
47
|
-
arel = arel.where(foreign_key_info.fk_type_column => foreign_key_info.fk_type) if foreign_key_info.polymorphic?
|
48
|
-
arel.order(:id).to_a
|
49
|
-
end
|
50
|
-
|
51
|
-
# Fetches primary key values to be used for new entities.
|
52
|
-
#
|
53
|
-
# @param class_config [ClassConfig] Config of the entity whose primary keys are being fetched.
|
54
|
-
# @return [[Integer]] An array of unused primary keys.
|
55
|
-
def get_primary_keys(class_config, count)
|
56
|
-
result = execute("select nextval($1) from generate_series(1,$2);", [sequence_name(class_config), count])
|
57
|
-
result.rows.map(&:first).map(&:to_i)
|
58
|
-
end
|
59
|
-
|
60
|
-
# Builds an ORM Class for accessing data in the given DB table.
|
61
|
-
#
|
62
|
-
# @param model_class [Class] The PORO class that we are creating a DB interface class for.
|
63
|
-
# @param table_name [String] Name of the DB table the DB class should interface with.
|
64
|
-
# @return [Class] ActiveRecord::Base Class
|
65
|
-
def build_db_class(model_class, table_name)
|
66
|
-
db_class = Class.new(ActiveRecord::Base) do
|
67
|
-
class << self
|
68
|
-
# This is overridden for two reasons:
|
69
|
-
# 1) For anonymous classes, #name normally returns nil. Class names in Ruby come from the
|
70
|
-
# name of the constant they are assigned to.
|
71
|
-
# 2) Because the default implementation for Class#name for anonymous classes is very, very
|
72
|
-
# slow. https://bugs.ruby-lang.org/issues/11119
|
73
|
-
# Remove this override once #2 has been fixed!
|
74
|
-
def name
|
75
|
-
@name ||= "Vorpal_generated_ActiveRecord__Base_class_for_#{vorpal_model_class_name}"
|
76
|
-
end
|
77
|
-
|
78
|
-
# Overridden because, like #name, the default implementation for anonymous classes is very,
|
79
|
-
# very slow.
|
80
|
-
def to_s
|
81
|
-
name
|
82
|
-
end
|
83
|
-
|
84
|
-
attr_accessor :vorpal_model_class_name
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
db_class.vorpal_model_class_name = Util::StringUtils.escape_class_name(model_class.name)
|
89
|
-
db_class.table_name = table_name
|
90
|
-
db_class
|
91
|
-
end
|
92
|
-
|
93
|
-
# Builds a composable query object (e.g. ActiveRecord::Relation) with Vorpal methods mixed in.
|
94
|
-
#
|
95
|
-
# @param class_config [ClassConfig] Config of the entity whose db representations should be returned.
|
96
|
-
def query(class_config, aggregate_mapper)
|
97
|
-
class_config.db_class.unscoped.extending(ArelQueryMethods.new(aggregate_mapper))
|
98
|
-
end
|
99
|
-
|
100
|
-
private
|
101
|
-
|
102
|
-
def sequence_name(class_config)
|
103
|
-
@sequence_names[class_config] ||= execute(
|
104
|
-
"SELECT substring(column_default from '''(.*)''') FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = $1 AND column_name = 'id' LIMIT 1",
|
105
|
-
[class_config.db_class.table_name]
|
106
|
-
).rows.first.first
|
107
|
-
end
|
108
|
-
|
109
|
-
def execute(sql, binds)
|
110
|
-
binds = binds.map { |row| [nil, row] }
|
111
|
-
ActiveRecord::Base.connection.exec_query(sql, 'SQL', binds)
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
class ArelQueryMethods < Module
|
116
|
-
def initialize(mapper)
|
117
|
-
@mapper = mapper
|
118
|
-
end
|
119
|
-
|
120
|
-
def extended(descendant)
|
121
|
-
super
|
122
|
-
descendant.extend(Methods)
|
123
|
-
descendant.vorpal_aggregate_mapper = @mapper
|
124
|
-
end
|
125
|
-
|
126
|
-
# Methods in this module will appear on any composable
|
127
|
-
module Methods
|
128
|
-
attr_writer :vorpal_aggregate_mapper
|
129
|
-
|
130
|
-
# See {AggregateMapper#load_many}.
|
131
|
-
def load_many
|
132
|
-
db_roots = self.to_a
|
133
|
-
@vorpal_aggregate_mapper.load_many(db_roots)
|
134
|
-
end
|
135
|
-
|
136
|
-
# See {AggregateMapper#load_one}.
|
137
|
-
def load_one
|
138
|
-
db_root = self.first
|
139
|
-
@vorpal_aggregate_mapper.load_one(db_root)
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|