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