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