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