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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 824912a10087771cda48b9ad66dac75861e80bb1
4
- data.tar.gz: 67ff5e87b3e45c8308866036e79cfb83ae5959d8
2
+ SHA256:
3
+ metadata.gz: 2fdca5b34f5a1ac2b9b228849c53b8322e542ab9bc75a5c15a0c8003f1f1fd94
4
+ data.tar.gz: aebdb05efdfd853644afa49bf101d845082a7d627e47c50bf609830be46901f0
5
5
  SHA512:
6
- metadata.gz: 3db514381c185a0dc4b9b4a408ca35b34a244bb1fe97e899ff1b89ebb84d14cd20d5159c2a459316d77c1e3b4d81e4529e0ff110d02dfdece3f06817531653b1
7
- data.tar.gz: 5437e2611a1ae1e75b1143cd1f9732429fa9acb0fb5e11fc105e60bb7d0330fe4e23021ca7c63b676758977fedeafaed2b39f26be24f83b1f431fc5c7ab1a0b2
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
@@ -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 {DbDriver}.
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, DbDriver.new)
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, DbDriver.new)
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Vorpal
2
- VERSION = "0.1.0.rc3"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -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::DbDriver.new
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::DbDriver do
4
+ describe Vorpal::Driver::Postgresql do
5
5
  describe '#build_db_class' do
6
- let(:db_class) { subject.build_db_class(DbDriverSpec::Foo, 'example') }
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(DbDriverSpec::Foo)
18
- db_class2 = build_db_class(DbDriverSpec::Bar)
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(DbDriverSpec::Foo)
25
+ db_class = build_db_class(PostgresDriverSpec::Foo)
26
26
 
27
- expect(db_class.name).to match("DbDriverSpec__Foo")
27
+ expect(db_class.name).to match("PostgresDriverSpec__Foo")
28
28
  end
29
29
  end
30
30
 
31
31
  private
32
32
 
33
- module DbDriverSpec
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::DbDriver.new
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::DbDriver.new
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::DbDriver")
94
- expect(driver).to receive(:load_by_id).with(post_config, [post_db.id]).and_return([post_db])
95
- expect(driver).to receive(:load_by_id).with(comment_config, [best_comment_db.id]).and_return([best_comment_db])
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,6 @@
1
1
  require 'unit_spec_helper'
2
2
 
3
3
  require 'vorpal/dsl/config_builder'
4
- require 'vorpal/db_driver'
5
4
 
6
5
  describe Vorpal::Dsl::ConfigBuilder do
7
6
  class Tester; end
@@ -1,7 +1,7 @@
1
1
  require 'unit_spec_helper'
2
2
 
3
3
  require 'vorpal/dsl/defaults_generator'
4
- require 'vorpal/db_driver'
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::DbDriver)}
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
@@ -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: 0.1.0.rc3
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: 2016-11-11 00:00:00.000000000 Z
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/db_driver_spec.rb
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: '0'
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: 1.3.1
206
+ version: '0'
208
207
  requirements: []
209
208
  rubyforge_project:
210
- rubygems_version: 2.4.8
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/db_driver_spec.rb
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
@@ -1 +0,0 @@
1
- 2.1.6
@@ -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