rom-sql 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,110 @@
|
|
1
|
+
RSpec.describe ROM::SQL::Association::ManyToOne, helpers: true do
|
2
|
+
subject(:assoc) {
|
3
|
+
ROM::SQL::Association::ManyToOne.new(:tasks, :users)
|
4
|
+
}
|
5
|
+
|
6
|
+
include_context 'users and tasks'
|
7
|
+
|
8
|
+
let(:users) { container.relations[:users] }
|
9
|
+
let(:tasks) { container.relations[:tasks] }
|
10
|
+
let(:articles) { container.relations[:articles] }
|
11
|
+
|
12
|
+
with_adapters do
|
13
|
+
before do
|
14
|
+
conf.relation(:tasks) do
|
15
|
+
schema do
|
16
|
+
attribute :id, ROM::SQL::Types::Serial
|
17
|
+
attribute :user_id, ROM::SQL::Types::ForeignKey(:users)
|
18
|
+
attribute :title, ROM::SQL::Types::String
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#result' do
|
24
|
+
specify { expect(ROM::SQL::Association::ManyToOne.result).to be(:one) }
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#name' do
|
28
|
+
it 'uses target by default' do
|
29
|
+
expect(assoc.name).to be(:users)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#target' do
|
34
|
+
it 'builds full relation name' do
|
35
|
+
assoc = ROM::SQL::Association::ManyToOne.new(:users, :tasks, relation: :foo)
|
36
|
+
|
37
|
+
expect(assoc.name).to be(:tasks)
|
38
|
+
expect(assoc.target).to eql(ROM::SQL::Association::Name[:foo, :tasks])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#call' do
|
43
|
+
it 'prepares joined relations' do
|
44
|
+
relation = assoc.call(container.relations)
|
45
|
+
|
46
|
+
expect(relation.attributes).to eql(%i[id name task_id])
|
47
|
+
|
48
|
+
expect(relation.where(user_id: 1).one).to eql(id: 1, task_id: 2, name: 'Jane')
|
49
|
+
|
50
|
+
expect(relation.where(user_id: 2).one).to eql(id: 2, task_id: 1, name: 'Joe')
|
51
|
+
|
52
|
+
expect(relation.to_a).to eql([
|
53
|
+
{ id: 2, task_id: 1, name: 'Joe' },
|
54
|
+
{ id: 1, task_id: 2, name: 'Jane' }
|
55
|
+
])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe ROM::Plugins::Relation::SQL::AutoCombine, '#for_combine' do
|
60
|
+
it 'preloads relation based on association' do
|
61
|
+
relation = users.for_combine(assoc).call(tasks.call)
|
62
|
+
|
63
|
+
expect(relation.to_a).to eql([
|
64
|
+
{ id: 2, task_id: 1, name: 'Joe' },
|
65
|
+
{ id: 1, task_id: 2, name: 'Jane' }
|
66
|
+
])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'arbitrary name conventions' do
|
71
|
+
let(:articles_name) { ROM::Relation::Name[:articles, :posts] }
|
72
|
+
|
73
|
+
subject(:assoc) do
|
74
|
+
ROM::SQL::Association::ManyToOne.new(articles_name, :users)
|
75
|
+
end
|
76
|
+
|
77
|
+
before do
|
78
|
+
conf.relation(:articles) do
|
79
|
+
schema(:posts) do
|
80
|
+
attribute :post_id, ROM::SQL::Types::Serial
|
81
|
+
attribute :author_id, ROM::SQL::Types::ForeignKey(:users)
|
82
|
+
attribute :title, ROM::SQL::Types::Strict::String
|
83
|
+
attribute :body, ROM::SQL::Types::Strict::String
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe '#call' do
|
89
|
+
it 'prepares joined relations' do
|
90
|
+
relation = assoc.call(container.relations)
|
91
|
+
|
92
|
+
expect(relation.attributes).to eql(%i[id name post_id])
|
93
|
+
|
94
|
+
expect(relation.order(:id).to_a).to eql([
|
95
|
+
{ id: 1, name: 'Jane', post_id: 2 },
|
96
|
+
{ id: 2, name: 'Joe', post_id: 1 }
|
97
|
+
])
|
98
|
+
|
99
|
+
expect(relation.where(author_id: 1).to_a).to eql(
|
100
|
+
[id: 1, name: 'Jane', post_id: 2]
|
101
|
+
)
|
102
|
+
|
103
|
+
expect(relation.where(author_id: 2).to_a).to eql(
|
104
|
+
[id: 2, name: 'Joe', post_id: 1]
|
105
|
+
)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
RSpec.describe ROM::SQL::Association::OneToMany do
|
2
|
+
subject(:assoc) {
|
3
|
+
ROM::SQL::Association::OneToMany.new(:users, :tasks)
|
4
|
+
}
|
5
|
+
|
6
|
+
include_context 'users and tasks'
|
7
|
+
|
8
|
+
let(:users) { container.relations[:users] }
|
9
|
+
let(:tasks) { container.relations[:tasks] }
|
10
|
+
|
11
|
+
with_adapters do
|
12
|
+
before do
|
13
|
+
conf.relation(:tasks) do
|
14
|
+
schema do
|
15
|
+
attribute :id, ROM::SQL::Types::Serial
|
16
|
+
attribute :user_id, ROM::SQL::Types::ForeignKey(:users)
|
17
|
+
attribute :title, ROM::SQL::Types::String
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#result' do
|
23
|
+
specify { expect(ROM::SQL::Association::OneToMany.result).to be(:many) }
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#call' do
|
27
|
+
it 'prepares joined relations' do
|
28
|
+
relation = assoc.call(container.relations)
|
29
|
+
|
30
|
+
expect(relation.attributes).to eql(%i[id user_id title])
|
31
|
+
|
32
|
+
expect(relation.order(:tasks__id).to_a).to eql([
|
33
|
+
{ id: 1, user_id: 2, title: "Joe's task" },
|
34
|
+
{ id: 2, user_id: 1, title: "Jane's task" }
|
35
|
+
])
|
36
|
+
|
37
|
+
expect(relation.where(user_id: 1).to_a).to eql([
|
38
|
+
{ id: 2, user_id: 1, title: "Jane's task" }
|
39
|
+
])
|
40
|
+
|
41
|
+
expect(relation.where(user_id: 2).to_a).to eql([
|
42
|
+
{ id: 1, user_id: 2, title: "Joe's task" }
|
43
|
+
])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe ROM::Plugins::Relation::SQL::AutoCombine, '#for_combine' do
|
48
|
+
it 'preloads relation based on association' do
|
49
|
+
relation = tasks.for_combine(assoc).call(users.call)
|
50
|
+
|
51
|
+
expect(relation.to_a).to eql([
|
52
|
+
{ id: 1, user_id: 2, title: "Joe's task" },
|
53
|
+
{ id: 2, user_id: 1, title: "Jane's task" }
|
54
|
+
])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
RSpec.describe ROM::SQL::Association::OneToOne do
|
2
|
+
subject(:assoc) {
|
3
|
+
ROM::SQL::Association::OneToOne.new(:users, :accounts)
|
4
|
+
}
|
5
|
+
|
6
|
+
include_context 'users and accounts'
|
7
|
+
|
8
|
+
let(:users) { container.relations[:users] }
|
9
|
+
let(:accounts) { container.relations[:accounts] }
|
10
|
+
|
11
|
+
with_adapters do
|
12
|
+
before do
|
13
|
+
conf.relation(:accounts) do
|
14
|
+
schema do
|
15
|
+
attribute :id, ROM::SQL::Types::Serial
|
16
|
+
attribute :user_id, ROM::SQL::Types::ForeignKey(:users)
|
17
|
+
attribute :number, ROM::SQL::Types::String
|
18
|
+
attribute :balance, ROM::SQL::Types::Decimal
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#result' do
|
24
|
+
specify { expect(ROM::SQL::Association::OneToOne.result).to be(:one) }
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#call' do
|
28
|
+
it 'prepares joined relations' do
|
29
|
+
relation = assoc.call(container.relations)
|
30
|
+
|
31
|
+
expect(relation.attributes).to eql(%i[id user_id number balance])
|
32
|
+
|
33
|
+
# TODO: this if caluse should be removed when (and if) https://github.com/xerial/sqlite-jdbc/issues/112
|
34
|
+
# will be resolved. See https://github.com/rom-rb/rom-sql/issues/49 for details
|
35
|
+
if defined? JRUBY_VERSION && SQLITE_DB_URI == db_uri
|
36
|
+
expect(relation.to_a).to eql([id: 1, user_id: 1, number: '42', balance: 10_000])
|
37
|
+
else
|
38
|
+
expect(relation.to_a).to eql([id: 1, user_id: 1, number: '42', balance: 10_000.to_d])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe ROM::Plugins::Relation::SQL::AutoCombine, '#for_combine' do
|
44
|
+
it 'preloads relation based on association' do
|
45
|
+
relation = accounts.for_combine(assoc).call(users.call)
|
46
|
+
|
47
|
+
# TODO: this if caluse should be removed when (and if) https://github.com/xerial/sqlite-jdbc/issues/112
|
48
|
+
# will be resolved. See https://github.com/rom-rb/rom-sql/issues/49 for details
|
49
|
+
if defined? JRUBY_VERSION && SQLITE_DB_URI == db_uri
|
50
|
+
expect(relation.to_a).to eql([id: 1, user_id: 1, number: '42', balance: 10_000])
|
51
|
+
else
|
52
|
+
expect(relation.to_a).to eql([id: 1, user_id: 1, number: '42', balance: 10_000.to_d])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
RSpec.describe ROM::SQL::Association::OneToOneThrough do
|
2
|
+
subject(:assoc) {
|
3
|
+
ROM::SQL::Association::OneToOneThrough.new(:users, :cards, through: :accounts)
|
4
|
+
}
|
5
|
+
|
6
|
+
include_context 'users and accounts'
|
7
|
+
|
8
|
+
let(:users) { container.relations[:users] }
|
9
|
+
let(:cards) { container.relations[:cards] }
|
10
|
+
|
11
|
+
with_adapters do
|
12
|
+
before do
|
13
|
+
conf.relation(:accounts) do
|
14
|
+
schema do
|
15
|
+
attribute :id, ROM::SQL::Types::Serial
|
16
|
+
attribute :user_id, ROM::SQL::Types::ForeignKey(:users)
|
17
|
+
attribute :number, ROM::SQL::Types::String
|
18
|
+
attribute :balance, ROM::SQL::Types::Decimal
|
19
|
+
|
20
|
+
associations do
|
21
|
+
one_to_many :cards
|
22
|
+
one_to_many :subscriptions, through: :cards
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
conf.relation(:cards) do
|
28
|
+
schema do
|
29
|
+
attribute :id, ROM::SQL::Types::Serial
|
30
|
+
attribute :account_id, ROM::SQL::Types::ForeignKey(:accounts)
|
31
|
+
attribute :pan, ROM::SQL::Types::String
|
32
|
+
|
33
|
+
associations do
|
34
|
+
one_to_many :subscriptions
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
conf.relation(:subscriptions) do
|
40
|
+
schema do
|
41
|
+
attribute :id, ROM::SQL::Types::Serial
|
42
|
+
attribute :card_id, ROM::SQL::Types::ForeignKey(:cards)
|
43
|
+
attribute :service, ROM::SQL::Types::String
|
44
|
+
|
45
|
+
associations do
|
46
|
+
many_to_one :cards
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#result' do
|
53
|
+
specify { expect(ROM::SQL::Association::OneToOneThrough.result).to be(:one) }
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#call' do
|
57
|
+
it 'prepares joined relations' do
|
58
|
+
relation = assoc.call(container.relations)
|
59
|
+
|
60
|
+
expect(relation.attributes).to eql(%i[id account_id pan user_id])
|
61
|
+
expect(relation.to_a).to eql([id: 1, account_id: 1, pan: '*6789', user_id: 1])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe ':through another assoc' do
|
66
|
+
subject(:assoc) do
|
67
|
+
ROM::SQL::Association::OneToOneThrough.new(:users, :subscriptions, through: :accounts)
|
68
|
+
end
|
69
|
+
|
70
|
+
let(:account_assoc) do
|
71
|
+
ROM::SQL::Association::OneToOneThrough.new(:accounts, :subscriptions, through: :cards)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'prepares joined relations through other association' do
|
75
|
+
relation = assoc.call(container.relations)
|
76
|
+
|
77
|
+
expect(relation.attributes).to eql(%i[id card_id service user_id])
|
78
|
+
expect(relation.to_a).to eql([id: 1, card_id: 1, service: 'aws', user_id: 1])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe ROM::Plugins::Relation::SQL::AutoCombine, '#for_combine' do
|
83
|
+
it 'preloads relation based on association' do
|
84
|
+
relation = cards.for_combine(assoc).call(users.call)
|
85
|
+
|
86
|
+
expect(relation.to_a).to eql([id: 1, account_id: 1, pan: '*6789', user_id: 1])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -1,37 +1,37 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
describe 'Eager loading' do
|
1
|
+
describe 'Eager loading', adapters: :all do
|
4
2
|
include_context 'users and tasks'
|
5
3
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
4
|
+
with_adapters do
|
5
|
+
before do
|
6
|
+
conf.relation(:users) do
|
7
|
+
def by_name(name)
|
8
|
+
where(name: name)
|
9
|
+
end
|
10
10
|
end
|
11
|
-
end
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
conf.relation(:tasks) do
|
13
|
+
def for_users(users)
|
14
|
+
where(user_id: users.map { |tuple| tuple[:id] })
|
15
|
+
end
|
16
16
|
end
|
17
|
-
end
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
conf.relation(:tags) do
|
19
|
+
def for_tasks(tasks)
|
20
|
+
inner_join(:task_tags, task_id: :id)
|
21
|
+
.where(task_id: tasks.map { |tuple| tuple[:id] })
|
22
|
+
end
|
23
23
|
end
|
24
24
|
end
|
25
|
-
end
|
26
25
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
it 'issues 3 queries for 3 combined relations' do
|
27
|
+
users = container.relation(:users).by_name('Piotr')
|
28
|
+
tasks = container.relation(:tasks)
|
29
|
+
tags = container.relation(:tags)
|
31
30
|
|
32
|
-
|
31
|
+
relation = users.combine(tasks.for_users.combine(tags.for_tasks))
|
33
32
|
|
34
|
-
|
35
|
-
|
33
|
+
# TODO: figure out a way to assert correct number of issued queries
|
34
|
+
expect(relation.call).to be_instance_of(ROM::Relation::Loaded)
|
35
|
+
end
|
36
36
|
end
|
37
37
|
end
|
@@ -1,11 +1,10 @@
|
|
1
|
-
require 'spec_helper'
|
2
1
|
require 'virtus'
|
3
2
|
|
4
|
-
describe 'Commands / Create' do
|
5
|
-
include_context '
|
3
|
+
RSpec.describe 'Commands / Create' do
|
4
|
+
include_context 'relations'
|
6
5
|
|
7
|
-
|
8
|
-
|
6
|
+
let(:users) { commands[:users] }
|
7
|
+
let(:tasks) { commands[:tasks] }
|
9
8
|
|
10
9
|
before do
|
11
10
|
class Params
|
@@ -18,7 +17,9 @@ describe 'Commands / Create' do
|
|
18
17
|
end
|
19
18
|
end
|
20
19
|
|
21
|
-
|
20
|
+
conn.add_index :users, :name, unique: true
|
21
|
+
|
22
|
+
conf.commands(:users) do
|
22
23
|
define(:create) do
|
23
24
|
input Params
|
24
25
|
|
@@ -34,225 +35,271 @@ describe 'Commands / Create' do
|
|
34
35
|
end
|
35
36
|
end
|
36
37
|
|
37
|
-
|
38
|
+
conf.commands(:tasks) do
|
38
39
|
define(:create)
|
39
40
|
end
|
40
|
-
|
41
|
-
configuration.relation(:users)
|
42
|
-
configuration.relation(:tasks)
|
43
41
|
end
|
44
42
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
users.create.
|
49
|
-
}
|
50
|
-
|
51
|
-
expect(result.value).to eq(id: 1, name: 'Jane')
|
52
|
-
end
|
53
|
-
|
54
|
-
it 'creates multiple records if nothing was raised' do
|
55
|
-
result = users.create.transaction {
|
56
|
-
users.create_many.call([{ name: 'Jane' }, { name: 'Jack' }])
|
57
|
-
}
|
58
|
-
|
59
|
-
expect(result.value).to match_array([
|
60
|
-
{ id: 1, name: 'Jane' }, { id: 2, name: 'Jack' }
|
61
|
-
])
|
62
|
-
end
|
63
|
-
|
64
|
-
it 'allows for nested transactions' do
|
65
|
-
result = users.create.transaction {
|
66
|
-
users.create.transaction {
|
43
|
+
with_adapters do
|
44
|
+
describe '#transaction' do
|
45
|
+
it 'creates record if nothing was raised' do
|
46
|
+
result = users.create.transaction {
|
67
47
|
users.create.call(name: 'Jane')
|
68
48
|
}
|
69
|
-
}
|
70
49
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
it 'creates nothing if command error was raised' do
|
75
|
-
expect {
|
76
|
-
passed = false
|
50
|
+
expect(result.value).to eq(id: 1, name: 'Jane')
|
51
|
+
end
|
77
52
|
|
53
|
+
it 'creates multiple records if nothing was raised' do
|
78
54
|
result = users.create.transaction {
|
79
|
-
users.
|
80
|
-
users.create.call(name: '')
|
81
|
-
} >-> _value {
|
82
|
-
passed = true
|
55
|
+
users.create_many.call([{ name: 'Jane' }, { name: 'Jack' }])
|
83
56
|
}
|
84
57
|
|
85
|
-
expect(result.value).to
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
end
|
90
|
-
|
91
|
-
it 'creates nothing if rollback was raised' do
|
92
|
-
expect {
|
93
|
-
passed = false
|
58
|
+
expect(result.value).to match_array([
|
59
|
+
{ id: 1, name: 'Jane' }, { id: 2, name: 'Jack' }
|
60
|
+
])
|
61
|
+
end
|
94
62
|
|
63
|
+
it 'allows for nested transactions' do
|
95
64
|
result = users.create.transaction {
|
96
|
-
users.create.
|
97
|
-
|
98
|
-
|
99
|
-
} >-> _value {
|
100
|
-
passed = true
|
65
|
+
users.create.transaction {
|
66
|
+
users.create.call(name: 'Jane')
|
67
|
+
}
|
101
68
|
}
|
102
69
|
|
103
|
-
expect(result.value).to
|
104
|
-
|
105
|
-
expect(passed).to be(false)
|
106
|
-
}.to_not change { container.relations.users.count }
|
107
|
-
end
|
70
|
+
expect(result.value).to eq(id: 1, name: 'Jane')
|
71
|
+
end
|
108
72
|
|
109
|
-
|
110
|
-
|
111
|
-
begin
|
73
|
+
it 'creates nothing if command error was raised' do
|
74
|
+
expect {
|
112
75
|
passed = false
|
113
76
|
|
114
|
-
users.create.transaction {
|
115
|
-
users.create.call(name: 'Jane')
|
77
|
+
result = users.create.transaction {
|
116
78
|
users.create.call(name: 'Jane')
|
79
|
+
users.create.call(name: '')
|
117
80
|
} >-> _value {
|
118
81
|
passed = true
|
119
82
|
}
|
120
|
-
|
121
|
-
expect(
|
83
|
+
|
84
|
+
expect(result.value).to be(nil)
|
85
|
+
expect(result.error.message).to eql('name cannot be empty')
|
122
86
|
expect(passed).to be(false)
|
123
|
-
|
124
|
-
|
125
|
-
end
|
87
|
+
}.to_not change { container.relations.users.count }
|
88
|
+
end
|
126
89
|
|
127
|
-
|
128
|
-
expect {
|
90
|
+
it 'creates nothing if rollback was raised' do
|
129
91
|
expect {
|
130
|
-
|
92
|
+
passed = false
|
93
|
+
|
94
|
+
result = users.create.transaction {
|
95
|
+
users.create.call(name: 'Jane')
|
131
96
|
users.create.call(name: 'John')
|
97
|
+
raise ROM::SQL::Rollback
|
98
|
+
} >-> _value {
|
99
|
+
passed = true
|
100
|
+
}
|
101
|
+
|
102
|
+
expect(result.value).to be(nil)
|
103
|
+
expect(result.error).to be(nil)
|
104
|
+
expect(passed).to be(false)
|
105
|
+
}.to_not change { container.relations.users.count }
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'creates nothing if constraint error was raised' do
|
109
|
+
expect {
|
110
|
+
begin
|
111
|
+
passed = false
|
112
|
+
|
132
113
|
users.create.transaction {
|
133
114
|
users.create.call(name: 'Jane')
|
134
|
-
|
115
|
+
users.create.call(name: 'Jane')
|
116
|
+
} >-> _value {
|
117
|
+
passed = true
|
135
118
|
}
|
136
|
-
|
137
|
-
|
138
|
-
|
119
|
+
rescue => error
|
120
|
+
expect(error).to be_instance_of(ROM::SQL::UniqueConstraintError)
|
121
|
+
expect(passed).to be(false)
|
122
|
+
end
|
123
|
+
}.to_not change { container.relations.users.count }
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'creates nothing if anything was raised in any nested transaction' do
|
127
|
+
expect {
|
128
|
+
expect {
|
129
|
+
users.create.transaction {
|
130
|
+
users.create.call(name: 'John')
|
131
|
+
users.create.transaction {
|
132
|
+
users.create.call(name: 'Jane')
|
133
|
+
raise Exception
|
134
|
+
}
|
135
|
+
}
|
136
|
+
}.to raise_error(Exception)
|
137
|
+
}.to_not change { container.relations.users.count }
|
138
|
+
end
|
139
139
|
end
|
140
|
-
end
|
141
140
|
|
142
|
-
|
143
|
-
|
141
|
+
it 'uses relation schema for the default input handler' do
|
142
|
+
conf.relation(:users) do
|
143
|
+
register_as :users_with_schema
|
144
144
|
|
145
|
-
|
146
|
-
|
145
|
+
schema do
|
146
|
+
attribute :id, ROM::SQL::Types::Serial
|
147
|
+
attribute :name, ROM::SQL::Types::String
|
148
|
+
end
|
149
|
+
end
|
147
150
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
+
conf.commands(:users_with_schema) do
|
152
|
+
define(:create) do
|
153
|
+
result :one
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
create = container.commands[:users_with_schema][:create]
|
158
|
+
|
159
|
+
expect(create.input[foo: 'bar', id: 1, name: 'Jane']).to eql(
|
160
|
+
id: 1, name: 'Jane'
|
161
|
+
)
|
151
162
|
end
|
152
163
|
|
153
|
-
|
154
|
-
|
155
|
-
])
|
156
|
-
end
|
164
|
+
it 'returns a single tuple when result is set to :one' do
|
165
|
+
result = users.try { users.create.call(name: 'Jane') }
|
157
166
|
|
158
|
-
|
159
|
-
|
160
|
-
users.try { users.create.call(name: nil) }
|
161
|
-
}.to raise_error(ROM::SQL::NotNullConstraintError)
|
162
|
-
end
|
167
|
+
expect(result.value).to eql(id: 1, name: 'Jane')
|
168
|
+
end
|
163
169
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
} >-> user {
|
169
|
-
users.try { users.create.call(name: user[:name]) }
|
170
|
-
}
|
171
|
-
}.to raise_error(ROM::SQL::UniqueConstraintError)
|
172
|
-
end
|
170
|
+
it 'returns tuples when result is set to :many' do
|
171
|
+
result = users.try do
|
172
|
+
users.create_many.call([{ name: 'Jane' }, { name: 'Jack' }])
|
173
|
+
end
|
173
174
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
}
|
179
|
-
}.to raise_error(ROM::SQL::CheckConstraintError, /name/)
|
180
|
-
end
|
175
|
+
expect(result.value.to_a).to match_array([
|
176
|
+
{ id: 1, name: 'Jane' }, { id: 2, name: 'Jack' }
|
177
|
+
])
|
178
|
+
end
|
181
179
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
}.to raise_error(ROM::SQL::ForeignKeyConstraintError, /user_id/)
|
188
|
-
end
|
180
|
+
it 're-raises not-null constraint violation error' do
|
181
|
+
expect {
|
182
|
+
users.try { users.create.call(name: nil) }
|
183
|
+
}.to raise_error(ROM::SQL::NotNullConstraintError)
|
184
|
+
end
|
189
185
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
186
|
+
it 're-raises uniqueness constraint violation error' do
|
187
|
+
expect {
|
188
|
+
users.try {
|
189
|
+
users.create.call(name: 'Jane')
|
190
|
+
} >-> user {
|
191
|
+
users.try { users.create.call(name: user[:name]) }
|
192
|
+
}
|
193
|
+
}.to raise_error(ROM::SQL::UniqueConstraintError)
|
194
|
+
end
|
196
195
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
196
|
+
it 're-raises fk constraint violation error' do
|
197
|
+
expect {
|
198
|
+
tasks.try {
|
199
|
+
tasks.create.call(user_id: 918_273_645)
|
200
|
+
}
|
201
|
+
}.to raise_error(ROM::SQL::ForeignKeyConstraintError, /user_id/)
|
202
|
+
end
|
203
203
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
204
|
+
it 're-raises database errors' do
|
205
|
+
expect {
|
206
|
+
Params.attribute :bogus_field
|
207
|
+
users.try { users.create.call(name: 'some name', bogus_field: 23) }
|
208
|
+
}.to raise_error(ROM::SQL::DatabaseError)
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'supports [] syntax instead of call' do
|
212
|
+
expect {
|
213
|
+
Params.attribute :bogus_field
|
214
|
+
users.try { users.create[name: 'some name', bogus_field: 23] }
|
215
|
+
}.to raise_error(ROM::SQL::DatabaseError)
|
216
|
+
end
|
217
|
+
|
218
|
+
describe '#execute' do
|
219
|
+
context 'with a single record' do
|
220
|
+
it 'materializes the result' do
|
221
|
+
result = container.command(:users).create.execute(name: 'Jane')
|
222
|
+
expect(result).to eq([
|
223
|
+
{ id: 1, name: 'Jane' }
|
224
|
+
])
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
context 'with multiple records' do
|
229
|
+
it 'materializes the results' do
|
230
|
+
result = container.command(:users).create.execute([
|
231
|
+
{ name: 'Jane' },
|
232
|
+
{ name: 'John' }
|
233
|
+
])
|
234
|
+
expect(result).to eq([
|
235
|
+
{ id: 1, name: 'Jane' },
|
236
|
+
{ id: 2, name: 'John' }
|
237
|
+
])
|
209
238
|
end
|
210
239
|
end
|
211
240
|
|
212
|
-
|
241
|
+
context 'with a composite pk' do
|
242
|
+
before do
|
243
|
+
conn.create_table?(:user_group) do
|
244
|
+
primary_key [:user_id, :group_id]
|
245
|
+
column :user_id, Integer, null: false
|
246
|
+
column :group_id, Integer, null: false
|
247
|
+
end
|
213
248
|
|
214
|
-
|
215
|
-
|
216
|
-
|
249
|
+
conf.relation(:user_group) do
|
250
|
+
schema(infer: true)
|
251
|
+
end
|
217
252
|
|
218
|
-
|
253
|
+
conf.commands(:user_group) do
|
254
|
+
define(:create) { result :one }
|
255
|
+
end
|
256
|
+
end
|
219
257
|
|
220
|
-
|
258
|
+
after do
|
259
|
+
conn.drop_table(:user_group)
|
260
|
+
end
|
221
261
|
|
222
|
-
|
223
|
-
|
224
|
-
{ id: 2, user_id: 1, title: 'Task two' }
|
225
|
-
])
|
226
|
-
end
|
262
|
+
it 'materializes the result' do
|
263
|
+
pending 'TODO: with a composite pk sequel returns 0 when inserting' if mysql?
|
227
264
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
result :
|
232
|
-
associates :user, key: [:user_id, :id]
|
265
|
+
command = container.commands[:user_group][:create]
|
266
|
+
result = command.call(user_id: 1, group_id: 2)
|
267
|
+
|
268
|
+
expect(result).to eql(user_id: 1, group_id: 2)
|
233
269
|
end
|
234
270
|
end
|
271
|
+
end
|
272
|
+
end
|
235
273
|
|
236
|
-
|
237
|
-
|
274
|
+
describe '#call', adapter: :postgres do
|
275
|
+
it 're-raises check constraint violation error', adapter: :postgres do
|
276
|
+
expect {
|
277
|
+
users.try {
|
278
|
+
users.create.call(name: 'J')
|
279
|
+
}
|
280
|
+
}.to raise_error(ROM::SQL::CheckConstraintError, /name/)
|
281
|
+
end
|
282
|
+
end
|
238
283
|
|
239
|
-
|
284
|
+
describe '#upsert', adapter: :postgres do
|
285
|
+
let(:task) { { title: 'task 1' } }
|
240
286
|
|
241
|
-
|
287
|
+
before { tasks.create.call(task) }
|
242
288
|
|
243
|
-
|
289
|
+
it 'raises error without upsert marker' do
|
290
|
+
expect {
|
291
|
+
tasks.create.call(task)
|
292
|
+
}.to raise_error(ROM::SQL::UniqueConstraintError)
|
244
293
|
end
|
245
294
|
|
246
|
-
it 'raises
|
247
|
-
expect {
|
248
|
-
configuration.commands(:tasks) do
|
249
|
-
define(:create) do
|
250
|
-
result :one
|
251
|
-
associates :user, key: [:user_id, :id]
|
252
|
-
associates :user, key: [:user_id, :id]
|
253
|
-
end
|
254
|
-
end
|
255
|
-
}.to raise_error(ArgumentError, /user/)
|
295
|
+
it 'raises no error for duplicated data' do
|
296
|
+
expect { tasks.create.upsert(task) }.to_not raise_error
|
256
297
|
end
|
257
|
-
|
298
|
+
|
299
|
+
it 'returns record data' do
|
300
|
+
expect(tasks.create.upsert(task, constraint: :tasks_title_key, update: { user_id: nil })).to eql([
|
301
|
+
id: 1, user_id: nil, title: 'task 1'
|
302
|
+
])
|
303
|
+
end
|
304
|
+
end if PG_LTE_95
|
258
305
|
end
|