vorpal 0.1.0.rc3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|