rom-sql 0.6.1 → 0.7.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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]
|