rom-sql 0.7.0 → 0.8.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 +4 -4
- data/.rspec +1 -0
- data/.travis.yml +12 -7
- data/CHANGELOG.md +28 -0
- data/Gemfile +6 -9
- data/README.md +5 -4
- data/circle.yml +10 -0
- data/lib/rom/plugins/relation/sql/auto_combine.rb +16 -3
- data/lib/rom/plugins/relation/sql/auto_wrap.rb +3 -2
- data/lib/rom/sql/association.rb +75 -0
- data/lib/rom/sql/association/many_to_many.rb +86 -0
- data/lib/rom/sql/association/many_to_one.rb +60 -0
- data/lib/rom/sql/association/name.rb +70 -0
- data/lib/rom/sql/association/one_to_many.rb +9 -0
- data/lib/rom/sql/association/one_to_one.rb +46 -0
- data/lib/rom/sql/association/one_to_one_through.rb +9 -0
- data/lib/rom/sql/commands.rb +2 -0
- data/lib/rom/sql/commands/create.rb +2 -2
- data/lib/rom/sql/commands/delete.rb +0 -1
- data/lib/rom/sql/commands/postgres.rb +76 -0
- data/lib/rom/sql/commands/update.rb +6 -3
- data/lib/rom/sql/commands_ext/postgres.rb +17 -0
- data/lib/rom/sql/gateway.rb +23 -15
- data/lib/rom/sql/header.rb +7 -1
- data/lib/rom/sql/plugin/assoc_macros.rb +3 -3
- data/lib/rom/sql/plugin/associates.rb +50 -9
- data/lib/rom/sql/qualified_attribute.rb +53 -0
- data/lib/rom/sql/relation.rb +76 -25
- data/lib/rom/sql/relation/reading.rb +138 -35
- data/lib/rom/sql/relation/writing.rb +21 -0
- data/lib/rom/sql/schema.rb +35 -0
- data/lib/rom/sql/schema/associations_dsl.rb +68 -0
- data/lib/rom/sql/schema/dsl.rb +27 -0
- data/lib/rom/sql/schema/inferrer.rb +80 -0
- data/lib/rom/sql/support/active_support_notifications.rb +27 -17
- data/lib/rom/sql/types.rb +11 -0
- data/lib/rom/sql/types/pg.rb +26 -0
- data/lib/rom/sql/version.rb +1 -1
- data/rom-sql.gemspec +4 -2
- data/spec/integration/association/many_to_many_spec.rb +137 -0
- data/spec/integration/association/many_to_one_spec.rb +110 -0
- data/spec/integration/association/one_to_many_spec.rb +58 -0
- data/spec/integration/association/one_to_one_spec.rb +57 -0
- data/spec/integration/association/one_to_one_through_spec.rb +90 -0
- data/spec/integration/combine_spec.rb +24 -24
- data/spec/integration/commands/create_spec.rb +215 -168
- data/spec/integration/commands/delete_spec.rb +88 -46
- data/spec/integration/commands/update_spec.rb +141 -60
- data/spec/integration/commands/upsert_spec.rb +83 -0
- data/spec/integration/gateway_spec.rb +9 -17
- data/spec/integration/migration_spec.rb +3 -5
- data/spec/integration/plugins/associates_spec.rb +168 -0
- data/spec/integration/plugins/auto_wrap_spec.rb +46 -0
- data/spec/integration/read_spec.rb +80 -77
- data/spec/integration/relation_schema_spec.rb +180 -0
- data/spec/integration/schema_inference_spec.rb +67 -0
- data/spec/integration/setup_spec.rb +22 -0
- data/spec/{support → integration/support}/active_support_notifications_spec.rb +0 -0
- data/spec/{support → integration/support}/rails_log_subscriber_spec.rb +0 -0
- data/spec/shared/database_setup.rb +46 -8
- data/spec/shared/relations.rb +8 -0
- data/spec/shared/users_and_accounts.rb +10 -0
- data/spec/shared/users_and_tasks.rb +20 -2
- data/spec/spec_helper.rb +64 -11
- data/spec/support/helpers.rb +9 -0
- data/spec/unit/association/many_to_many_spec.rb +89 -0
- data/spec/unit/association/many_to_one_spec.rb +81 -0
- data/spec/unit/association/name_spec.rb +68 -0
- data/spec/unit/association/one_to_many_spec.rb +62 -0
- data/spec/unit/association/one_to_one_spec.rb +62 -0
- data/spec/unit/association/one_to_one_through_spec.rb +69 -0
- data/spec/unit/association_errors_spec.rb +2 -4
- data/spec/unit/gateway_spec.rb +12 -3
- data/spec/unit/migration_tasks_spec.rb +3 -3
- data/spec/unit/migrator_spec.rb +2 -4
- data/spec/unit/{combined_associations_spec.rb → plugin/assoc_macros/combined_associations_spec.rb} +13 -19
- data/spec/unit/{many_to_many_spec.rb → plugin/assoc_macros/many_to_many_spec.rb} +9 -15
- data/spec/unit/{many_to_one_spec.rb → plugin/assoc_macros/many_to_one_spec.rb} +9 -14
- data/spec/unit/plugin/assoc_macros/one_to_many_spec.rb +78 -0
- data/spec/unit/plugin/base_view_spec.rb +11 -11
- data/spec/unit/plugin/pagination_spec.rb +62 -62
- data/spec/unit/relation_spec.rb +218 -146
- data/spec/unit/schema_spec.rb +15 -14
- data/spec/unit/types_spec.rb +40 -0
- metadata +105 -21
- data/.rubocop.yml +0 -74
- data/.rubocop_todo.yml +0 -21
- data/spec/unit/one_to_many_spec.rb +0 -83
@@ -1,67 +1,109 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
describe 'Commands / Delete' do
|
1
|
+
RSpec.describe 'Commands / Delete' do
|
4
2
|
include_context 'users and tasks'
|
5
3
|
|
6
4
|
subject(:users) { container.commands.users }
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
6
|
+
with_adapters do
|
7
|
+
before do
|
8
|
+
conf.relation(:users) do
|
9
|
+
def by_name(name)
|
10
|
+
where(name: name)
|
11
|
+
end
|
12
12
|
end
|
13
|
-
end
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
conf.commands(:users) do
|
15
|
+
define(:delete) do
|
16
|
+
result :one
|
17
|
+
end
|
18
18
|
end
|
19
|
+
|
20
|
+
container.relations.users.insert(id: 3, name: 'Jade')
|
21
|
+
container.relations.users.insert(id: 4, name: 'John')
|
19
22
|
end
|
20
23
|
|
21
|
-
|
22
|
-
|
24
|
+
describe '#transaction' do
|
25
|
+
it 'deletes in normal way if no error raised' do
|
26
|
+
expect {
|
27
|
+
users.delete.transaction do
|
28
|
+
users.delete.by_name('Jade').call
|
29
|
+
end
|
30
|
+
}.to change { container.relations.users.count }.by(-1)
|
31
|
+
end
|
23
32
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
33
|
+
it 'deletes nothing if error was raised' do
|
34
|
+
expect {
|
35
|
+
users.delete.transaction do
|
36
|
+
users.delete.by_name('Jade').call
|
37
|
+
raise ROM::SQL::Rollback
|
38
|
+
end
|
39
|
+
}.to_not change { container.relations.users.count }
|
40
|
+
end
|
31
41
|
end
|
32
42
|
|
33
|
-
|
34
|
-
|
35
|
-
users.delete.
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
}.to_not change { container.relations.users.count }
|
40
|
-
end
|
41
|
-
end
|
43
|
+
describe '#call' do
|
44
|
+
it 'deletes all tuples in a restricted relation' do
|
45
|
+
result = users.try { users.delete.by_name('Jade').call }
|
46
|
+
|
47
|
+
expect(result.value).to eql(id: 3, name: 'Jade')
|
48
|
+
end
|
42
49
|
|
43
|
-
|
44
|
-
|
50
|
+
it 're-raises database error' do
|
51
|
+
command = users.delete.by_name('Jade')
|
45
52
|
|
46
|
-
|
47
|
-
|
48
|
-
|
53
|
+
expect(command.relation).to receive(:delete).and_raise(
|
54
|
+
Sequel::DatabaseError, 'totally wrong'
|
55
|
+
)
|
49
56
|
|
50
|
-
|
51
|
-
|
57
|
+
expect {
|
58
|
+
users.try { command.call }
|
59
|
+
}.to raise_error(ROM::SQL::DatabaseError, /totally wrong/)
|
60
|
+
end
|
61
|
+
end
|
52
62
|
|
53
|
-
|
54
|
-
|
63
|
+
describe '#execute' do
|
64
|
+
context 'with postgres adapter' do
|
65
|
+
context 'with a single record' do
|
66
|
+
it 'materializes the result' do
|
67
|
+
result = container.command(:users).delete.by_name(%w(Jade)).execute
|
68
|
+
expect(result).to eq([
|
69
|
+
{ id: 3, name: 'Jade' }
|
70
|
+
])
|
71
|
+
end
|
72
|
+
end
|
55
73
|
|
56
|
-
|
57
|
-
|
74
|
+
context 'with multiple records' do
|
75
|
+
it 'materializes the results' do
|
76
|
+
result = container.command(:users).delete.by_name(%w(Jade John)).execute
|
77
|
+
expect(result).to eq([
|
78
|
+
{ id: 3, name: 'Jade' },
|
79
|
+
{ id: 4, name: 'John' }
|
80
|
+
])
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'with other adapter', adapter: :sqlite do
|
86
|
+
let(:uri) { SQLITE_DB_URI }
|
58
87
|
|
59
|
-
|
60
|
-
|
61
|
-
|
88
|
+
context 'with a single record' do
|
89
|
+
it 'materializes the result' do
|
90
|
+
result = container.command(:users).delete.by_name(%w(Jade)).execute
|
91
|
+
expect(result).to eq([
|
92
|
+
{ id: 3, name: 'Jade' }
|
93
|
+
])
|
94
|
+
end
|
95
|
+
end
|
62
96
|
|
63
|
-
|
64
|
-
|
65
|
-
|
97
|
+
context 'with multiple records' do
|
98
|
+
it 'materializes the results' do
|
99
|
+
result = container.command(:users).delete.by_name(%w(Jade John)).execute
|
100
|
+
expect(result).to eq([
|
101
|
+
{ id: 3, name: 'Jade' },
|
102
|
+
{ id: 4, name: 'John' }
|
103
|
+
])
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
66
108
|
end
|
67
109
|
end
|
@@ -1,91 +1,172 @@
|
|
1
|
-
require 'spec_helper'
|
2
1
|
require 'anima'
|
3
2
|
|
4
|
-
describe 'Commands / Update' do
|
3
|
+
RSpec.describe 'Commands / Update' do
|
5
4
|
include_context 'database setup'
|
6
5
|
|
7
6
|
subject(:users) { container.command(:users) }
|
8
7
|
|
8
|
+
let(:update) { container.commands[:users][:update] }
|
9
|
+
|
9
10
|
let(:relation) { container.relations.users }
|
10
11
|
let(:piotr) { relation.by_name('Piotr').one }
|
11
12
|
let(:peter) { { name: 'Peter' } }
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
with_adapters do
|
15
|
+
context 'with a schema' do
|
16
|
+
before do
|
17
|
+
conf.relation(:users) do
|
18
|
+
schema do
|
19
|
+
attribute :id, ROM::SQL::Types::Serial
|
20
|
+
attribute :name, ROM::SQL::Types::String
|
21
|
+
end
|
22
|
+
end
|
17
23
|
end
|
18
24
|
|
19
|
-
|
20
|
-
|
25
|
+
it 'uses relation schema for the default input handler' do
|
26
|
+
conf.commands(:users) do
|
27
|
+
define(:update) do
|
28
|
+
result :one
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
expect(update.input[foo: 'bar', id: 1, name: 'Jane']).to eql(
|
33
|
+
id: 1, name: 'Jane'
|
34
|
+
)
|
21
35
|
end
|
22
36
|
end
|
23
37
|
|
24
|
-
|
25
|
-
|
26
|
-
|
38
|
+
context 'without a schema' do
|
39
|
+
before do
|
40
|
+
conf.relation(:users) do
|
41
|
+
def by_id(id)
|
42
|
+
where(id: id).limit(1)
|
43
|
+
end
|
27
44
|
|
28
|
-
|
45
|
+
def by_name(name)
|
46
|
+
where(name: name)
|
47
|
+
end
|
48
|
+
end
|
29
49
|
|
30
|
-
|
31
|
-
|
32
|
-
|
50
|
+
conf.commands(:users) do
|
51
|
+
define(:update)
|
52
|
+
end
|
33
53
|
|
34
|
-
|
35
|
-
end
|
54
|
+
Test::User = Class.new { include Anima.new(:id, :name) }
|
36
55
|
|
37
|
-
|
56
|
+
conf.mappers do
|
57
|
+
register :users, entity: -> tuples { tuples.map { |tuple| Test::User.new(tuple) } }
|
58
|
+
end
|
38
59
|
|
39
|
-
|
40
|
-
|
41
|
-
result = users.update.transaction do
|
42
|
-
users.update.by_id(piotr[:id]).call(peter)
|
60
|
+
relation.insert(name: 'Piotr')
|
61
|
+
relation.insert(name: 'Jane')
|
43
62
|
end
|
44
63
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
users.update.transaction do
|
50
|
-
users.update.by_id(piotr[:id]).call(peter)
|
51
|
-
raise ROM::SQL::Rollback
|
64
|
+
it 'respects configured input handler' do
|
65
|
+
expect(update.input[foo: 'bar', id: 1, name: 'Jane']).to eql(
|
66
|
+
foo: 'bar', id: 1, name: 'Jane'
|
67
|
+
)
|
52
68
|
end
|
53
69
|
|
54
|
-
|
55
|
-
|
56
|
-
|
70
|
+
context '#transaction' do
|
71
|
+
it 'update record if there was no errors' do
|
72
|
+
result = users.update.transaction do
|
73
|
+
users.update.by_id(piotr[:id]).call(peter)
|
74
|
+
end
|
57
75
|
|
58
|
-
|
59
|
-
|
60
|
-
users.update.by_id(piotr[:id]).call(peter)
|
61
|
-
end
|
76
|
+
expect(result.value).to eq([{ id: 1, name: 'Peter' }])
|
77
|
+
end
|
62
78
|
|
63
|
-
|
64
|
-
|
79
|
+
it 'updates nothing if error was raised' do
|
80
|
+
users.update.transaction do
|
81
|
+
users.update.by_id(piotr[:id]).call(peter)
|
82
|
+
raise ROM::SQL::Rollback
|
83
|
+
end
|
65
84
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
end
|
70
|
-
|
71
|
-
expect(result.value.to_a).to match_array([User.new(id: 1, name: 'Peter')])
|
72
|
-
end
|
73
|
-
|
74
|
-
it 'does not update when attributes did not change' do
|
75
|
-
result = users.try do
|
76
|
-
command = users.update.by_id(piotr[:id]).change(piotr)
|
77
|
-
|
78
|
-
expect(command.relation).not_to receive(:update)
|
85
|
+
expect(relation.first[:name]).to eql('Piotr')
|
86
|
+
end
|
87
|
+
end
|
79
88
|
|
80
|
-
|
89
|
+
describe '#call' do
|
90
|
+
it 'updates everything when there is no original tuple' do
|
91
|
+
result = users.try do
|
92
|
+
users.update.by_id(piotr[:id]).call(peter)
|
93
|
+
end
|
94
|
+
|
95
|
+
expect(result.value.to_a).to match_array([{ id: 1, name: 'Peter' }])
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'updates when attributes changed' do
|
99
|
+
result = users.try do
|
100
|
+
users.as(:entity).update.by_id(piotr[:id]).change(Test::User.new(piotr)).call(peter)
|
101
|
+
end
|
102
|
+
|
103
|
+
expect(result.value.to_a).to match_array([Test::User.new(id: 1, name: 'Peter')])
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'does not update when attributes did not change' do
|
107
|
+
result = users.try do
|
108
|
+
command = users.update.by_id(piotr[:id]).change(piotr)
|
109
|
+
|
110
|
+
expect(command.relation).not_to receive(:update)
|
111
|
+
|
112
|
+
command.call(name: piotr[:name])
|
113
|
+
end
|
114
|
+
|
115
|
+
expect(result.value.to_a).to be_empty
|
116
|
+
end
|
117
|
+
|
118
|
+
it 're-reaises database errors' do
|
119
|
+
expect {
|
120
|
+
users.try { users.update.by_id(piotr[:id]).call(bogus_field: '#trollface') }
|
121
|
+
}.to raise_error(ROM::SQL::DatabaseError, /bogus_field/)
|
122
|
+
end
|
123
|
+
|
124
|
+
describe '#execute' do
|
125
|
+
context 'with postgres adapter' do
|
126
|
+
context 'with a single record' do
|
127
|
+
it 'materializes the result' do
|
128
|
+
result = users.update.by_name('Piotr').execute(name: 'Pete')
|
129
|
+
expect(result).to eq([
|
130
|
+
{ id: 1, name: 'Pete' }
|
131
|
+
])
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context 'with multiple records' do
|
136
|
+
it 'materializes the results' do
|
137
|
+
result = users.update.by_name(%w(Piotr Jane)).execute(name: 'Josie')
|
138
|
+
expect(result).to eq([
|
139
|
+
{ id: 1, name: 'Josie' },
|
140
|
+
{ id: 2, name: 'Josie' }
|
141
|
+
])
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
context 'with other adapter', adapter: :sqlite do
|
147
|
+
let(:uri) { SQLITE_DB_URI }
|
148
|
+
|
149
|
+
context 'with a single record' do
|
150
|
+
it 'materializes the result' do
|
151
|
+
result = users.update.by_name('Piotr').execute(name: 'Pete')
|
152
|
+
expect(result).to eq([
|
153
|
+
{ id: 1, name: 'Pete' }
|
154
|
+
])
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
context 'with multiple records' do
|
159
|
+
it 'materializes the results' do
|
160
|
+
result = users.update.by_name(%w(Piotr Jane)).execute(name: 'Josie')
|
161
|
+
expect(result).to eq([
|
162
|
+
{ id: 1, name: 'Josie' },
|
163
|
+
{ id: 2, name: 'Josie' }
|
164
|
+
])
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
81
170
|
end
|
82
|
-
|
83
|
-
expect(result.value.to_a).to be_empty
|
84
|
-
end
|
85
|
-
|
86
|
-
it 're-reaises database errors' do
|
87
|
-
expect {
|
88
|
-
users.try { users.update.by_id(piotr[:id]).call(bogus_field: '#trollface') }
|
89
|
-
}.to raise_error(ROM::SQL::DatabaseError, /bogus_field/)
|
90
171
|
end
|
91
172
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
RSpec.describe 'Commands / Postgres / Upsert', adapter: :postgres do
|
2
|
+
subject(:command) { commands[:tasks][:create_or_update] }
|
3
|
+
|
4
|
+
include_context 'relations'
|
5
|
+
|
6
|
+
let(:tasks) { commands[:tasks] }
|
7
|
+
|
8
|
+
before do
|
9
|
+
conn[:users].insert id: 1, name: 'Jane'
|
10
|
+
conn[:users].insert id: 2, name: 'Joe'
|
11
|
+
conn[:users].insert id: 3, name: 'Jean'
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#call' do
|
15
|
+
let(:task) { { title: 'task 1', user_id: 1 } }
|
16
|
+
let(:excluded) { task.merge(user_id: 3) }
|
17
|
+
|
18
|
+
before do
|
19
|
+
command_config = self.command_config
|
20
|
+
|
21
|
+
conf.commands(:tasks) do
|
22
|
+
define('Postgres::Upsert') do
|
23
|
+
register_as :create_or_update
|
24
|
+
result :one
|
25
|
+
|
26
|
+
instance_exec(&command_config)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
before { command.relation.upsert(task) }
|
32
|
+
|
33
|
+
context 'on conflict do nothing' do
|
34
|
+
let(:command_config) { -> { } }
|
35
|
+
|
36
|
+
it 'returns nil' do
|
37
|
+
expect(command.call(excluded)).to be nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'on conflict do update' do
|
42
|
+
context 'with conflict target' do
|
43
|
+
let(:command_config) do
|
44
|
+
-> do
|
45
|
+
conflict_target :title
|
46
|
+
update_statement user_id: 2
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'returns updated data' do
|
51
|
+
expect(command.call(excluded)).to eql(id: 1, user_id: 2, title: 'task 1')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'with constraint name' do
|
56
|
+
let(:command_config) do
|
57
|
+
-> do
|
58
|
+
constraint :tasks_title_key
|
59
|
+
update_statement user_id: :excluded__user_id
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'returns updated data' do
|
64
|
+
expect(command.call(excluded)).to eql(id: 1, user_id: 3, title: 'task 1')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'with where clause' do
|
69
|
+
let(:command_config) do
|
70
|
+
-> do
|
71
|
+
conflict_target :title
|
72
|
+
update_statement user_id: nil
|
73
|
+
update_where tasks__id: 2
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'returns nil' do
|
78
|
+
expect(command.call(excluded)).to be nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end if PG_LTE_95
|