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
@@ -1,14 +1,12 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
1
|
describe ROM::SQL::Gateway do
|
4
2
|
describe 'migration' do
|
5
|
-
let(:conn) { Sequel.connect(
|
3
|
+
let(:conn) { Sequel.connect(POSTGRES_DB_URI) }
|
6
4
|
|
7
5
|
context 'creating migrations inline' do
|
8
6
|
subject(:gateway) { container.gateways[:default] }
|
9
7
|
|
10
|
-
let(:
|
11
|
-
let!(:container) { ROM.container(
|
8
|
+
let(:conf) { ROM::Configuration.new(:sql, conn) }
|
9
|
+
let!(:container) { ROM.container(conf) }
|
12
10
|
|
13
11
|
after do
|
14
12
|
[:rabbits, :carrots].each do |name|
|
@@ -48,8 +46,8 @@ describe ROM::SQL::Gateway do
|
|
48
46
|
end
|
49
47
|
|
50
48
|
let(:migrator) { ROM::SQL::Migration::Migrator.new(conn, path: migration_dir) }
|
51
|
-
let(:
|
52
|
-
let!(:container) { ROM.container(
|
49
|
+
let(:conf) { ROM::Configuration.new(:sql, [conn, migrator: migrator]) }
|
50
|
+
let!(:container) { ROM.container(conf) }
|
53
51
|
|
54
52
|
it 'returns true for pending migrations' do
|
55
53
|
expect(container.gateways[:default].pending_migrations?).to be_truthy
|
@@ -70,31 +68,25 @@ describe ROM::SQL::Gateway do
|
|
70
68
|
include_context 'database setup'
|
71
69
|
|
72
70
|
it 'skips settings up associations when tables are missing' do
|
73
|
-
|
74
|
-
config.use(:macros)
|
75
|
-
|
71
|
+
conf = ROM::Configuration.new(:sql, uri) do |config|
|
76
72
|
config.relation(:foos) do
|
77
73
|
use :assoc_macros
|
78
|
-
primary_key :id
|
79
74
|
one_to_many :bars, key: :foo_id
|
80
75
|
end
|
81
76
|
end
|
82
|
-
expect { ROM.container(
|
77
|
+
expect { ROM.container(conf) }.not_to raise_error
|
83
78
|
end
|
84
79
|
|
85
80
|
it 'skips finalization a relation when table is missing' do
|
86
|
-
|
87
|
-
config.use(:macros)
|
88
|
-
|
81
|
+
conf = ROM::Configuration.new(:sql, uri) do |config|
|
89
82
|
class Foos < ROM::Relation[:sql]
|
90
83
|
dataset :foos
|
91
84
|
use :assoc_macros
|
92
|
-
primary_key :id
|
93
85
|
one_to_many :bars, key: :foo_id
|
94
86
|
end
|
95
87
|
end
|
96
88
|
|
97
|
-
expect { ROM.container(
|
89
|
+
expect { ROM.container(conf) }.not_to raise_error
|
98
90
|
expect { Foos.model.dataset }.to raise_error(Sequel::Error, /no dataset/i)
|
99
91
|
end
|
100
92
|
end
|
@@ -1,11 +1,9 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
describe ROM::SQL, '.migration' do
|
1
|
+
RSpec.describe ROM::SQL, '.migration' do
|
4
2
|
let(:connection) { ROM::SQL.gateway.connection }
|
5
|
-
let(:
|
3
|
+
let(:conf) { ROM::Configuration.new(:sql, POSTGRES_DB_URI) }
|
6
4
|
|
7
5
|
before do
|
8
|
-
|
6
|
+
conf
|
9
7
|
connection.drop_table?(:dragons)
|
10
8
|
end
|
11
9
|
|
@@ -0,0 +1,168 @@
|
|
1
|
+
RSpec.describe 'Plugins / :associates' do
|
2
|
+
include_context 'relations'
|
3
|
+
|
4
|
+
with_adapters do
|
5
|
+
context 'with Create command' do
|
6
|
+
let(:users) { container.commands[:users] }
|
7
|
+
let(:tasks) { container.commands[:tasks] }
|
8
|
+
let(:tags) { container.commands[:tags] }
|
9
|
+
|
10
|
+
before do
|
11
|
+
conf.commands(:users) do
|
12
|
+
define(:create) { result :one }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
shared_context 'automatic FK setting' do
|
17
|
+
it 'sets foreign key prior execution for many tuples' do
|
18
|
+
create_user = users[:create].with(name: 'Jade')
|
19
|
+
create_task = tasks[:create_many].with([{ title: 'Task one' }, { title: 'Task two' }])
|
20
|
+
|
21
|
+
command = create_user >> create_task
|
22
|
+
|
23
|
+
result = command.call
|
24
|
+
|
25
|
+
expect(result).to match_array([
|
26
|
+
{ id: 1, user_id: 1, title: 'Task one' },
|
27
|
+
{ id: 2, user_id: 1, title: 'Task two' }
|
28
|
+
])
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'sets foreign key prior execution for one tuple' do
|
32
|
+
create_user = users[:create].with(name: 'Jade')
|
33
|
+
create_task = tasks[:create_one].with(title: 'Task one')
|
34
|
+
|
35
|
+
command = create_user >> create_task
|
36
|
+
|
37
|
+
result = command.call
|
38
|
+
|
39
|
+
expect(result).to match_array(id: 1, user_id: 1, title: 'Task one')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'without a schema' do
|
44
|
+
include_context 'automatic FK setting' do
|
45
|
+
before do
|
46
|
+
conf.commands(:tasks) do
|
47
|
+
define(:create) do
|
48
|
+
register_as :create_many
|
49
|
+
associates :user, key: [:user_id, :id]
|
50
|
+
end
|
51
|
+
|
52
|
+
define(:create) do
|
53
|
+
register_as :create_one
|
54
|
+
result :one
|
55
|
+
associates :user, key: [:user_id, :id]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'with a schema' do
|
63
|
+
include_context 'automatic FK setting'
|
64
|
+
|
65
|
+
before do
|
66
|
+
conf.relation_classes[1].class_eval do
|
67
|
+
schema do
|
68
|
+
attribute :id, ROM::SQL::Types::Serial
|
69
|
+
attribute :user_id, ROM::SQL::Types::ForeignKey(:users)
|
70
|
+
attribute :title, ROM::SQL::Types::String
|
71
|
+
|
72
|
+
associations do
|
73
|
+
many_to_one :users, as: :user
|
74
|
+
one_to_many :task_tags
|
75
|
+
one_to_many :tags, through: :task_tags
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
conf.commands(:tasks) do
|
81
|
+
define(:create) do
|
82
|
+
register_as :create_many
|
83
|
+
associates :user
|
84
|
+
end
|
85
|
+
|
86
|
+
define(:create) do
|
87
|
+
register_as :create_one
|
88
|
+
result :one
|
89
|
+
associates :user
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'with many-to-many association' do
|
95
|
+
before do
|
96
|
+
conf.relation(:tags) do
|
97
|
+
schema do
|
98
|
+
attribute :id, ROM::SQL::Types::Serial
|
99
|
+
attribute :name, ROM::SQL::Types::String
|
100
|
+
|
101
|
+
associations do
|
102
|
+
one_to_many :task_tags
|
103
|
+
one_to_many :tasks, through: :task_tags
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
conf.relation(:task_tags) do
|
109
|
+
schema do
|
110
|
+
attribute :tag_id, ROM::SQL::Types::ForeignKey(:tags)
|
111
|
+
attribute :task_id, ROM::SQL::Types::ForeignKey(:tasks)
|
112
|
+
|
113
|
+
primary_key :tag_id, :task_id
|
114
|
+
|
115
|
+
associations do
|
116
|
+
many_to_one :tags
|
117
|
+
many_to_one :tasks
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
conf.commands(:tasks) do
|
123
|
+
define(:create) do
|
124
|
+
result :one
|
125
|
+
associates :user
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
conf.commands(:tags) do
|
130
|
+
define(:create) do
|
131
|
+
associates :tasks
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'sets FKs for the join table' do
|
137
|
+
create_user = users[:create].with(name: 'Jade')
|
138
|
+
create_task = tasks[:create].with(title: "Jade's task")
|
139
|
+
create_tags = tags[:create].with([{ name: 'red' }, { name: 'blue' }])
|
140
|
+
|
141
|
+
command = create_user >> create_task >> create_tags
|
142
|
+
|
143
|
+
result = command.call
|
144
|
+
tags = relations[:tasks].associations[:tags].call(relations).to_a
|
145
|
+
|
146
|
+
expect(result).to eql([
|
147
|
+
{ id: 1, task_id: 1, name: 'red' }, { id: 2, task_id: 1, name: 'blue' }
|
148
|
+
])
|
149
|
+
|
150
|
+
expect(tags).to eql(result)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'raises when already defined' do
|
156
|
+
expect {
|
157
|
+
conf.commands(:tasks) do
|
158
|
+
define(:create) do
|
159
|
+
result :one
|
160
|
+
associates :user, key: [:user_id, :id]
|
161
|
+
associates :user, key: [:user_id, :id]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
}.to raise_error(ArgumentError, /user/)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
RSpec.describe 'Plugins / :auto_wrap' do
|
2
|
+
with_adapters do
|
3
|
+
include_context 'users and tasks'
|
4
|
+
|
5
|
+
describe '#for_wrap' do
|
6
|
+
shared_context 'joined tuple' do
|
7
|
+
it 'returns joined tuples' do
|
8
|
+
task_with_user = tasks
|
9
|
+
.for_wrap({ id: :user_id }, users.name.relation)
|
10
|
+
.where(tasks__id: 2)
|
11
|
+
.one
|
12
|
+
|
13
|
+
expect(task_with_user).to eql(
|
14
|
+
id: 2, user_id: 1, title: "Jane's task", users_name: "Jane", users_id: 1
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when parent relation is registered under dataset name' do
|
20
|
+
subject(:tasks) { container.relations[:tasks] }
|
21
|
+
|
22
|
+
let(:users) { container.relations[:users] }
|
23
|
+
|
24
|
+
before do
|
25
|
+
conf.relation(:tasks)
|
26
|
+
conf.relation(:users)
|
27
|
+
end
|
28
|
+
|
29
|
+
include_context 'joined tuple'
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when parent relation is registered under a custom name' do
|
33
|
+
subject(:tasks) { container.relations[:tasks] }
|
34
|
+
|
35
|
+
let(:users) { container.relations[:authors] }
|
36
|
+
|
37
|
+
before do
|
38
|
+
conf.relation(:tasks)
|
39
|
+
conf.relation(:authors) { dataset :users }
|
40
|
+
end
|
41
|
+
|
42
|
+
include_context 'joined tuple'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -1,116 +1,119 @@
|
|
1
|
-
require 'spec_helper'
|
2
1
|
require 'virtus'
|
3
2
|
|
4
|
-
describe 'Reading relations' do
|
3
|
+
RSpec.describe 'Reading relations' do
|
5
4
|
include_context 'users and tasks'
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
# FIXME: one example fails on :mysql
|
7
|
+
with_adapters(:postgres, :sqlite) do
|
8
|
+
before :each do
|
9
|
+
class Goal
|
10
|
+
include Virtus.value_object(coerce: true)
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
12
|
+
values do
|
13
|
+
attribute :id, Integer
|
14
|
+
attribute :title, String
|
15
|
+
end
|
14
16
|
end
|
15
|
-
end
|
16
17
|
|
17
|
-
|
18
|
-
|
18
|
+
class User
|
19
|
+
include Virtus.value_object(coerce: true)
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
values do
|
22
|
+
attribute :id, Integer
|
23
|
+
attribute :name, String
|
24
|
+
attribute :goals, Array[Goal]
|
25
|
+
end
|
24
26
|
end
|
25
|
-
end
|
26
27
|
|
27
|
-
|
28
|
-
|
28
|
+
class UserGoalCount
|
29
|
+
include Virtus.value_object(coerce: true)
|
29
30
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
values do
|
32
|
+
attribute :id, Integer
|
33
|
+
attribute :name, String
|
34
|
+
attribute :goal_count, Integer
|
35
|
+
end
|
34
36
|
end
|
35
|
-
end
|
36
37
|
|
37
|
-
|
38
|
-
|
38
|
+
conf.relation(:goals) do
|
39
|
+
use :assoc_macros
|
39
40
|
|
40
|
-
|
41
|
-
|
42
|
-
|
41
|
+
register_as :goals
|
42
|
+
dataset :tasks
|
43
|
+
end
|
43
44
|
|
44
|
-
|
45
|
-
|
45
|
+
conf.relation(:users) do
|
46
|
+
use :assoc_macros
|
46
47
|
|
47
|
-
|
48
|
+
one_to_many :goals, key: :user_id
|
48
49
|
|
49
|
-
|
50
|
-
|
51
|
-
|
50
|
+
def by_name(name)
|
51
|
+
where(name: name)
|
52
|
+
end
|
52
53
|
|
53
|
-
|
54
|
-
|
55
|
-
|
54
|
+
def with_goals
|
55
|
+
association_left_join(:goals, select: [:id, :title])
|
56
|
+
end
|
56
57
|
|
57
|
-
|
58
|
-
|
58
|
+
def all
|
59
|
+
select(:id, :name)
|
60
|
+
end
|
59
61
|
end
|
60
|
-
end
|
61
62
|
|
62
|
-
|
63
|
-
|
63
|
+
conf.relation(:user_goal_counts) do
|
64
|
+
use :assoc_macros
|
64
65
|
|
65
|
-
|
66
|
-
|
67
|
-
|
66
|
+
dataset :users
|
67
|
+
register_as :user_goal_counts
|
68
|
+
one_to_many :goals, key: :user_id
|
68
69
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
70
|
+
def all
|
71
|
+
with_goals.select_group(:users__id, :users__name).select_append {
|
72
|
+
count(:tasks).as(:goal_count)
|
73
|
+
}
|
74
|
+
end
|
74
75
|
|
75
|
-
|
76
|
-
|
76
|
+
def with_goals
|
77
|
+
association_left_join(:goals, select: [:id, :title])
|
78
|
+
end
|
77
79
|
end
|
78
|
-
end
|
79
80
|
|
80
|
-
|
81
|
-
|
82
|
-
|
81
|
+
conf.mappers do
|
82
|
+
define(:users) do
|
83
|
+
model User
|
83
84
|
|
84
|
-
|
85
|
-
|
85
|
+
group :goals do
|
86
|
+
model Goal
|
86
87
|
|
87
|
-
|
88
|
-
|
88
|
+
attribute :id, from: :tasks_id
|
89
|
+
attribute :title
|
90
|
+
end
|
89
91
|
end
|
90
|
-
end
|
91
92
|
|
92
|
-
|
93
|
-
|
93
|
+
define(:user_goal_counts) do
|
94
|
+
model UserGoalCount
|
95
|
+
end
|
94
96
|
end
|
95
97
|
end
|
96
|
-
end
|
97
98
|
|
98
|
-
|
99
|
-
|
99
|
+
it 'loads domain objects' do
|
100
|
+
user = container.relation(:users).as(:users).with_goals.by_name('Jane').to_a.first
|
100
101
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
102
|
+
expect(user).to eql(
|
103
|
+
User.new(
|
104
|
+
id: 1, name: 'Jane', goals: [Goal.new(id: 2, title: "Jane's task")]
|
105
|
+
))
|
106
|
+
end
|
106
107
|
|
107
|
-
|
108
|
-
|
108
|
+
it 'works with grouping and aggregates' do
|
109
|
+
container.relations[:goals].insert(id: 3, user_id: 1, title: 'Get Milk')
|
109
110
|
|
110
|
-
|
111
|
+
users_with_goal_count = container.relation(:user_goal_counts).as(:user_goal_counts).all
|
111
112
|
|
112
|
-
|
113
|
-
|
114
|
-
|
113
|
+
expect(users_with_goal_count.to_a).to eq([
|
114
|
+
UserGoalCount.new(id: 1, name: "Jane", goal_count: 2),
|
115
|
+
UserGoalCount.new(id: 2, name: "Joe", goal_count: 1)
|
116
|
+
])
|
117
|
+
end
|
115
118
|
end
|
116
119
|
end
|