rom-sql 0.6.1 → 0.7.0.beta1
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 +4 -4
- data/.rubocop.yml +1 -5
- data/.rubocop_todo.yml +12 -6
- data/.travis.yml +3 -2
- data/CHANGELOG.md +27 -0
- data/Gemfile +1 -0
- data/lib/rom/plugins/relation/sql/auto_combine.rb +45 -0
- data/lib/rom/plugins/relation/sql/auto_wrap.rb +48 -0
- data/lib/rom/plugins/relation/sql/base_view.rb +31 -0
- data/lib/rom/sql.rb +17 -10
- data/lib/rom/sql/commands/error_wrapper.rb +1 -1
- data/lib/rom/sql/commands/update.rb +1 -1
- data/lib/rom/sql/gateway.rb +8 -2
- data/lib/rom/sql/header.rb +1 -1
- data/lib/rom/sql/migration.rb +11 -20
- data/lib/rom/sql/migration/migrator.rb +4 -0
- data/lib/rom/sql/{relation/associations.rb → plugin/assoc_macros.rb} +17 -2
- data/lib/rom/sql/plugin/assoc_macros/class_interface.rb +128 -0
- data/lib/rom/sql/plugin/associates.rb +2 -4
- data/lib/rom/sql/plugin/pagination.rb +1 -1
- data/lib/rom/sql/plugins.rb +4 -2
- data/lib/rom/sql/relation.rb +46 -6
- data/lib/rom/sql/relation/reading.rb +63 -0
- data/lib/rom/sql/tasks/migration_tasks.rake +37 -16
- data/lib/rom/sql/version.rb +1 -1
- data/rom-sql.gemspec +2 -2
- data/spec/fixtures/migrations/20150403090603_create_carrots.rb +1 -1
- data/spec/integration/combine_spec.rb +6 -6
- data/spec/integration/commands/create_spec.rb +25 -25
- data/spec/integration/commands/delete_spec.rb +6 -6
- data/spec/integration/commands/update_spec.rb +5 -5
- data/spec/integration/{repository_spec.rb → gateway_spec.rb} +34 -21
- data/spec/integration/migration_spec.rb +3 -3
- data/spec/integration/read_spec.rb +13 -7
- data/spec/shared/database_setup.rb +3 -5
- data/spec/spec_helper.rb +3 -6
- data/spec/support/active_support_notifications_spec.rb +1 -1
- data/spec/support/rails_log_subscriber_spec.rb +1 -1
- data/spec/unit/association_errors_spec.rb +4 -3
- data/spec/unit/combined_associations_spec.rb +7 -5
- data/spec/unit/gateway_spec.rb +2 -2
- data/spec/unit/logger_spec.rb +1 -1
- data/spec/unit/many_to_many_spec.rb +7 -4
- data/spec/unit/many_to_one_spec.rb +14 -8
- data/spec/unit/migration_tasks_spec.rb +7 -6
- data/spec/unit/one_to_many_spec.rb +16 -10
- data/spec/unit/plugin/base_view_spec.rb +18 -0
- data/spec/unit/plugin/pagination_spec.rb +10 -10
- data/spec/unit/relation_spec.rb +69 -3
- data/spec/unit/schema_spec.rb +5 -3
- metadata +19 -26
- data/lib/rom/sql/relation/class_methods.rb +0 -116
- data/lib/rom/sql/relation/inspection.rb +0 -16
@@ -64,13 +64,11 @@ module ROM
|
|
64
64
|
# @api public
|
65
65
|
def associates(name, options)
|
66
66
|
if associations.include?(name)
|
67
|
-
raise
|
68
|
-
ArgumentError,
|
67
|
+
raise ArgumentError,
|
69
68
|
"#{name} association is already defined for #{self.class}"
|
70
|
-
)
|
71
69
|
end
|
72
70
|
|
73
|
-
option :association, reader: true, default: ->
|
71
|
+
option :association, reader: true, default: -> _command { options }
|
74
72
|
include InstanceMethods
|
75
73
|
|
76
74
|
associations << name
|
data/lib/rom/sql/plugins.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'rom/sql/plugin/assoc_macros'
|
2
|
+
require 'rom/sql/plugin/associates'
|
3
|
+
require 'rom/sql/plugin/pagination'
|
3
4
|
|
4
5
|
ROM.plugins do
|
5
6
|
adapter :sql do
|
7
|
+
register :assoc_macros, ROM::SQL::Plugin::AssocMacros, type: :relation
|
6
8
|
register :pagination, ROM::SQL::Plugin::Pagination, type: :relation
|
7
9
|
register :associates, ROM::SQL::Plugin::Associates, type: :command
|
8
10
|
end
|
data/lib/rom/sql/relation.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
require 'rom/sql/header'
|
2
2
|
|
3
|
-
require 'rom/sql/relation/class_methods'
|
4
3
|
require 'rom/sql/relation/reading'
|
5
4
|
require 'rom/sql/relation/writing'
|
6
|
-
|
7
|
-
require 'rom/
|
5
|
+
|
6
|
+
require 'rom/plugins/relation/view'
|
7
|
+
require 'rom/plugins/relation/key_inference'
|
8
|
+
require 'rom/plugins/relation/sql/base_view'
|
9
|
+
require 'rom/plugins/relation/sql/auto_combine'
|
10
|
+
require 'rom/plugins/relation/sql/auto_wrap'
|
8
11
|
|
9
12
|
module ROM
|
10
13
|
module SQL
|
@@ -13,10 +16,12 @@ module ROM
|
|
13
16
|
class Relation < ROM::Relation
|
14
17
|
adapter :sql
|
15
18
|
|
16
|
-
|
19
|
+
use :key_inference
|
20
|
+
use :view
|
21
|
+
use :base_view
|
22
|
+
use :auto_combine
|
23
|
+
use :auto_wrap
|
17
24
|
|
18
|
-
include Inspection
|
19
|
-
include Associations
|
20
25
|
include Writing
|
21
26
|
include Reading
|
22
27
|
|
@@ -28,6 +33,41 @@ module ROM
|
|
28
33
|
# @attr_reader [Symbol] table
|
29
34
|
attr_reader :table
|
30
35
|
|
36
|
+
# Set default dataset for a relation sub-class
|
37
|
+
#
|
38
|
+
# @api private
|
39
|
+
def self.inherited(klass)
|
40
|
+
super
|
41
|
+
|
42
|
+
klass.class_eval do
|
43
|
+
dataset do
|
44
|
+
table = opts[:from].first
|
45
|
+
|
46
|
+
if db.table_exists?(table)
|
47
|
+
# quick fix for dbs w/o primary_key inference
|
48
|
+
#
|
49
|
+
# TODO: add a way of setting a pk explicitly on a relation
|
50
|
+
pk =
|
51
|
+
if db.respond_to?(:primary_key)
|
52
|
+
Array(db.primary_key(table))
|
53
|
+
else
|
54
|
+
[:id]
|
55
|
+
end.map { |name| :"#{table}__#{name}" }
|
56
|
+
|
57
|
+
select(*columns).order(*pk)
|
58
|
+
else
|
59
|
+
self
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.primary_key(value)
|
66
|
+
option :primary_key, reader: true, default: value
|
67
|
+
end
|
68
|
+
|
69
|
+
primary_key :id
|
70
|
+
|
31
71
|
# @api private
|
32
72
|
def initialize(dataset, registry = {})
|
33
73
|
super
|
@@ -175,6 +175,54 @@ module ROM
|
|
175
175
|
__new__(dataset.__send__(__method__, *args, &block))
|
176
176
|
end
|
177
177
|
|
178
|
+
# Returns a result of SQL SUM clause.
|
179
|
+
#
|
180
|
+
# @example
|
181
|
+
# users.sum(:age)
|
182
|
+
#
|
183
|
+
# @return Number
|
184
|
+
#
|
185
|
+
# @api public
|
186
|
+
def sum(*args)
|
187
|
+
dataset.__send__(__method__, *args)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Returns a result of SQL MIN clause.
|
191
|
+
#
|
192
|
+
# @example
|
193
|
+
# users.min(:age)
|
194
|
+
#
|
195
|
+
# @return Number
|
196
|
+
#
|
197
|
+
# @api public
|
198
|
+
def min(*args)
|
199
|
+
dataset.__send__(__method__, *args)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Returns a result of SQL MAX clause.
|
203
|
+
#
|
204
|
+
# @example
|
205
|
+
# users.max(:age)
|
206
|
+
#
|
207
|
+
# @return Number
|
208
|
+
#
|
209
|
+
# @api public
|
210
|
+
def max(*args)
|
211
|
+
dataset.__send__(__method__, *args)
|
212
|
+
end
|
213
|
+
|
214
|
+
# Returns a result of SQL AVG clause.
|
215
|
+
#
|
216
|
+
# @example
|
217
|
+
# users.avg(:age)
|
218
|
+
#
|
219
|
+
# @return Number
|
220
|
+
#
|
221
|
+
# @api public
|
222
|
+
def avg(*args)
|
223
|
+
dataset.__send__(__method__, *args)
|
224
|
+
end
|
225
|
+
|
178
226
|
# Restrict a relation to match criteria
|
179
227
|
#
|
180
228
|
# @example
|
@@ -323,6 +371,21 @@ module ROM
|
|
323
371
|
def select_group(*args, &block)
|
324
372
|
__new__(dataset.__send__(__method__, *args, &block))
|
325
373
|
end
|
374
|
+
|
375
|
+
# Adds a UNION clause for relation dataset using second relation dataset
|
376
|
+
#
|
377
|
+
# @param [Relation] relation object
|
378
|
+
#
|
379
|
+
# @example
|
380
|
+
# users.where(id: 1).union(users.where(id: 2))
|
381
|
+
# # => [{ id: 1, name: 'Piotr' }, { id: 2, name: 'Jane' }]
|
382
|
+
#
|
383
|
+
# @return [Relation]
|
384
|
+
#
|
385
|
+
# @api public
|
386
|
+
def union(relation, *args, &block)
|
387
|
+
__new__(dataset.__send__(__method__, relation.dataset, *args, &block))
|
388
|
+
end
|
326
389
|
end
|
327
390
|
end
|
328
391
|
end
|
@@ -1,41 +1,62 @@
|
|
1
1
|
require "pathname"
|
2
2
|
require "fileutils"
|
3
3
|
|
4
|
+
module ROM
|
5
|
+
module SQL
|
6
|
+
module RakeSupport
|
7
|
+
class << self
|
8
|
+
def run_migrations(options = {})
|
9
|
+
gateway.run_migrations(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def create_migration(*args)
|
13
|
+
gateway.migrator.create_file(*args)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def gateway
|
19
|
+
ROM::SQL::Gateway.instance
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
4
26
|
namespace :db do
|
5
27
|
desc "Perform migration reset (full erase and migration up)"
|
6
|
-
task
|
7
|
-
|
8
|
-
|
9
|
-
|
28
|
+
task :rom_configuration do
|
29
|
+
Rake::Task["db:setup"].invoke
|
30
|
+
end
|
31
|
+
|
32
|
+
task reset: :rom_configuration do
|
33
|
+
ROM::SQL::RakeSupport.run_migrations(target: 0)
|
34
|
+
ROM::SQL::RakeSupport.run_migrations
|
10
35
|
puts "<= db:reset executed"
|
11
36
|
end
|
12
37
|
|
13
38
|
desc "Migrate the database (options [version_number])]"
|
14
|
-
task :migrate, [:version] => :
|
15
|
-
gateway = ROM::SQL.gateway
|
39
|
+
task :migrate, [:version] => :rom_configuration do |_, args|
|
16
40
|
version = args[:version]
|
17
41
|
|
18
42
|
if version.nil?
|
19
|
-
|
43
|
+
ROM::SQL::RakeSupport.run_migrations
|
20
44
|
puts "<= db:migrate executed"
|
21
45
|
else
|
22
|
-
|
46
|
+
ROM::SQL::RakeSupport.run_migrations(target: version.to_i)
|
23
47
|
puts "<= db:migrate version=[#{version}] executed"
|
24
48
|
end
|
25
49
|
end
|
26
50
|
|
27
51
|
desc "Perform migration down (erase all data)"
|
28
|
-
task clean: :
|
29
|
-
|
30
|
-
|
31
|
-
gateway.run_migrations(target: 0)
|
52
|
+
task clean: :rom_configuration do
|
53
|
+
ROM::SQL::RakeSupport.run_migrations(target: 0)
|
32
54
|
puts "<= db:clean executed"
|
33
55
|
end
|
34
56
|
|
35
57
|
desc "Create a migration (parameters: NAME, VERSION)"
|
36
|
-
task :create_migration, [:name, :version] => :
|
37
|
-
|
38
|
-
name, version = args[:name], args[:version]
|
58
|
+
task :create_migration, [:name, :version] => :rom_configuration do |_, args|
|
59
|
+
name, version = args.values_at(:name, :version)
|
39
60
|
|
40
61
|
if name.nil?
|
41
62
|
puts "No NAME specified. Example usage:
|
@@ -43,7 +64,7 @@ namespace :db do
|
|
43
64
|
exit
|
44
65
|
end
|
45
66
|
|
46
|
-
path =
|
67
|
+
path = ROM::SQL::RakeSupport.create_migration(*[name, version].compact)
|
47
68
|
|
48
69
|
puts "<= migration file created #{path}"
|
49
70
|
end
|
data/lib/rom/sql/version.rb
CHANGED
data/rom-sql.gemspec
CHANGED
@@ -19,8 +19,8 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_runtime_dependency "sequel", "~> 4.18"
|
22
|
-
spec.add_runtime_dependency "equalizer", "~> 0.
|
23
|
-
spec.add_runtime_dependency "rom", "~> 0.
|
22
|
+
spec.add_runtime_dependency "dry-equalizer", "~> 0.2"
|
23
|
+
spec.add_runtime_dependency "rom", "~> 1.0.0.beta1"
|
24
24
|
|
25
25
|
spec.add_development_dependency "bundler"
|
26
26
|
spec.add_development_dependency "rake", "~> 10.0"
|
@@ -4,19 +4,19 @@ describe 'Eager loading' do
|
|
4
4
|
include_context 'users and tasks'
|
5
5
|
|
6
6
|
before do
|
7
|
-
|
7
|
+
configuration.relation(:users) do
|
8
8
|
def by_name(name)
|
9
9
|
where(name: name)
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
|
13
|
+
configuration.relation(:tasks) do
|
14
14
|
def for_users(users)
|
15
15
|
where(user_id: users.map { |tuple| tuple[:id] })
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
|
19
|
+
configuration.relation(:tags) do
|
20
20
|
def for_tasks(tasks)
|
21
21
|
inner_join(:task_tags, task_id: :id)
|
22
22
|
.where(task_id: tasks.map { |tuple| tuple[:id] })
|
@@ -25,9 +25,9 @@ describe 'Eager loading' do
|
|
25
25
|
end
|
26
26
|
|
27
27
|
it 'issues 3 queries for 3 combined relations' do
|
28
|
-
users =
|
29
|
-
tasks =
|
30
|
-
tags =
|
28
|
+
users = container.relation(:users).by_name('Piotr')
|
29
|
+
tasks = container.relation(:tasks)
|
30
|
+
tags = container.relation(:tags)
|
31
31
|
|
32
32
|
relation = users.combine(tasks.for_users.combine(tags.for_tasks))
|
33
33
|
|
@@ -4,8 +4,8 @@ require 'virtus'
|
|
4
4
|
describe 'Commands / Create' do
|
5
5
|
include_context 'database setup'
|
6
6
|
|
7
|
-
subject(:users) {
|
8
|
-
subject(:tasks) {
|
7
|
+
subject(:users) { container.commands.users }
|
8
|
+
subject(:tasks) { container.commands.tasks }
|
9
9
|
|
10
10
|
before do
|
11
11
|
class Params
|
@@ -18,12 +18,12 @@ describe 'Commands / Create' do
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
|
21
|
+
configuration.commands(:users) do
|
22
22
|
define(:create) do
|
23
23
|
input Params
|
24
24
|
|
25
25
|
validator -> tuple {
|
26
|
-
raise ROM::CommandError
|
26
|
+
raise ROM::CommandError, 'name cannot be empty' if tuple[:name] == ''
|
27
27
|
}
|
28
28
|
|
29
29
|
result :one
|
@@ -34,12 +34,12 @@ describe 'Commands / Create' do
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
|
37
|
+
configuration.commands(:tasks) do
|
38
38
|
define(:create)
|
39
39
|
end
|
40
40
|
|
41
|
-
|
42
|
-
|
41
|
+
configuration.relation(:users)
|
42
|
+
configuration.relation(:tasks)
|
43
43
|
end
|
44
44
|
|
45
45
|
context '#transaction' do
|
@@ -53,7 +53,7 @@ describe 'Commands / Create' do
|
|
53
53
|
|
54
54
|
it 'creates multiple records if nothing was raised' do
|
55
55
|
result = users.create.transaction {
|
56
|
-
users.create_many.call([{name: 'Jane'}, {name: 'Jack'}])
|
56
|
+
users.create_many.call([{ name: 'Jane' }, { name: 'Jack' }])
|
57
57
|
}
|
58
58
|
|
59
59
|
expect(result.value).to match_array([
|
@@ -78,14 +78,14 @@ describe 'Commands / Create' do
|
|
78
78
|
result = users.create.transaction {
|
79
79
|
users.create.call(name: 'Jane')
|
80
80
|
users.create.call(name: '')
|
81
|
-
} >->
|
81
|
+
} >-> _value {
|
82
82
|
passed = true
|
83
83
|
}
|
84
84
|
|
85
85
|
expect(result.value).to be(nil)
|
86
86
|
expect(result.error.message).to eql('name cannot be empty')
|
87
87
|
expect(passed).to be(false)
|
88
|
-
}.to_not change {
|
88
|
+
}.to_not change { container.relations.users.count }
|
89
89
|
end
|
90
90
|
|
91
91
|
it 'creates nothing if rollback was raised' do
|
@@ -96,14 +96,14 @@ describe 'Commands / Create' do
|
|
96
96
|
users.create.call(name: 'Jane')
|
97
97
|
users.create.call(name: 'John')
|
98
98
|
raise ROM::SQL::Rollback
|
99
|
-
} >->
|
99
|
+
} >-> _value {
|
100
100
|
passed = true
|
101
101
|
}
|
102
102
|
|
103
103
|
expect(result.value).to be(nil)
|
104
104
|
expect(result.error).to be(nil)
|
105
105
|
expect(passed).to be(false)
|
106
|
-
}.to_not change {
|
106
|
+
}.to_not change { container.relations.users.count }
|
107
107
|
end
|
108
108
|
|
109
109
|
it 'creates nothing if constraint error was raised' do
|
@@ -114,14 +114,14 @@ describe 'Commands / Create' do
|
|
114
114
|
users.create.transaction {
|
115
115
|
users.create.call(name: 'Jane')
|
116
116
|
users.create.call(name: 'Jane')
|
117
|
-
} >->
|
117
|
+
} >-> _value {
|
118
118
|
passed = true
|
119
119
|
}
|
120
120
|
rescue => error
|
121
121
|
expect(error).to be_instance_of(ROM::SQL::UniqueConstraintError)
|
122
122
|
expect(passed).to be(false)
|
123
123
|
end
|
124
|
-
}.to_not change {
|
124
|
+
}.to_not change { container.relations.users.count }
|
125
125
|
end
|
126
126
|
|
127
127
|
it 'creates nothing if anything was raised in any nested transaction' do
|
@@ -135,7 +135,7 @@ describe 'Commands / Create' do
|
|
135
135
|
}
|
136
136
|
}
|
137
137
|
}.to raise_error(Exception)
|
138
|
-
}.to_not change {
|
138
|
+
}.to_not change { container.relations.users.count }
|
139
139
|
end
|
140
140
|
end
|
141
141
|
|
@@ -158,7 +158,7 @@ describe 'Commands / Create' do
|
|
158
158
|
it 're-raises not-null constraint violation error' do
|
159
159
|
expect {
|
160
160
|
users.try { users.create.call(name: nil) }
|
161
|
-
}.to raise_error(ROM::SQL::NotNullConstraintError
|
161
|
+
}.to raise_error(ROM::SQL::NotNullConstraintError)
|
162
162
|
end
|
163
163
|
|
164
164
|
it 're-raises uniqueness constraint violation error' do
|
@@ -168,7 +168,7 @@ describe 'Commands / Create' do
|
|
168
168
|
} >-> user {
|
169
169
|
users.try { users.create.call(name: user[:name]) }
|
170
170
|
}
|
171
|
-
}.to raise_error(ROM::SQL::UniqueConstraintError
|
171
|
+
}.to raise_error(ROM::SQL::UniqueConstraintError)
|
172
172
|
end
|
173
173
|
|
174
174
|
it 're-raises check constraint violation error' do
|
@@ -182,7 +182,7 @@ describe 'Commands / Create' do
|
|
182
182
|
it 're-raises fk constraint violation error' do
|
183
183
|
expect {
|
184
184
|
tasks.try {
|
185
|
-
tasks.create.call(user_id:
|
185
|
+
tasks.create.call(user_id: 918_273_645)
|
186
186
|
}
|
187
187
|
}.to raise_error(ROM::SQL::ForeignKeyConstraintError, /user_id/)
|
188
188
|
end
|
@@ -203,15 +203,15 @@ describe 'Commands / Create' do
|
|
203
203
|
|
204
204
|
describe '.associates' do
|
205
205
|
it 'sets foreign key prior execution for many tuples' do
|
206
|
-
|
206
|
+
configuration.commands(:tasks) do
|
207
207
|
define(:create) do
|
208
208
|
associates :user, key: [:user_id, :id]
|
209
209
|
end
|
210
210
|
end
|
211
211
|
|
212
|
-
create_user =
|
212
|
+
create_user = container.command(:users).create.with(name: 'Jade')
|
213
213
|
|
214
|
-
create_task =
|
214
|
+
create_task = container.command(:tasks).create.with([
|
215
215
|
{ title: 'Task one' }, { title: 'Task two' }
|
216
216
|
])
|
217
217
|
|
@@ -226,15 +226,15 @@ describe 'Commands / Create' do
|
|
226
226
|
end
|
227
227
|
|
228
228
|
it 'sets foreign key prior execution for one tuple' do
|
229
|
-
|
229
|
+
configuration.commands(:tasks) do
|
230
230
|
define(:create) do
|
231
231
|
result :one
|
232
232
|
associates :user, key: [:user_id, :id]
|
233
233
|
end
|
234
234
|
end
|
235
235
|
|
236
|
-
create_user =
|
237
|
-
create_task =
|
236
|
+
create_user = container.command(:users).create.with(name: 'Jade')
|
237
|
+
create_task = container.command(:tasks).create.with(title: 'Task one')
|
238
238
|
|
239
239
|
command = create_user >> create_task
|
240
240
|
|
@@ -245,7 +245,7 @@ describe 'Commands / Create' do
|
|
245
245
|
|
246
246
|
it 'raises when already defined' do
|
247
247
|
expect {
|
248
|
-
|
248
|
+
configuration.commands(:tasks) do
|
249
249
|
define(:create) do
|
250
250
|
result :one
|
251
251
|
associates :user, key: [:user_id, :id]
|