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
data/spec/unit/relation_spec.rb
CHANGED
@@ -6,195 +6,267 @@ describe ROM::Relation do
|
|
6
6
|
let(:users) { container.relations.users }
|
7
7
|
let(:tasks) { container.relations.tasks }
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
context 'with schema', adapter: :sqlite do
|
10
|
+
let(:uri) { SQLITE_DB_URI }
|
11
|
+
|
12
|
+
before do
|
13
|
+
conf.relation(:users) do
|
14
|
+
schema do
|
15
|
+
attribute :id, ROM::SQL::Types::Serial
|
16
|
+
attribute :name, ROM::SQL::Types::String
|
17
|
+
end
|
18
|
+
|
19
|
+
def sorted
|
20
|
+
order(:id)
|
21
|
+
end
|
13
22
|
end
|
14
|
-
end
|
15
|
-
|
16
|
-
configuration.relation(:tasks)
|
17
|
-
end
|
18
23
|
|
19
|
-
|
20
|
-
it 'selects all qualified columns and sorts by pk' do
|
21
|
-
expect(users.dataset).to eql(
|
22
|
-
users.select(*users.columns).order(:users__id).dataset
|
23
|
-
)
|
24
|
+
conf.relation(:tasks)
|
24
25
|
end
|
25
|
-
end
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
27
|
+
describe '#dataset' do
|
28
|
+
it 'uses schema to infer default dataset' do
|
29
|
+
expect(container.relations[:users].dataset).to eql(
|
30
|
+
container.gateways[:default].dataset(:users).select(:id, :name).order(:users__id)
|
31
|
+
)
|
32
|
+
end
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
-
|
36
|
+
with_adapters do
|
37
|
+
context 'without schema' do
|
38
|
+
before do
|
39
|
+
conf.relation(:users) do
|
40
|
+
def sorted
|
41
|
+
order(:id)
|
42
|
+
end
|
43
|
+
end
|
37
44
|
|
38
|
-
|
39
|
-
|
40
|
-
end
|
41
|
-
end
|
45
|
+
conf.relation(:tasks)
|
46
|
+
end
|
42
47
|
|
43
|
-
|
44
|
-
|
45
|
-
|
48
|
+
describe '#associations' do
|
49
|
+
it 'returns an empty association set' do
|
50
|
+
expect(users.associations.elements).to be_empty
|
51
|
+
end
|
52
|
+
end
|
46
53
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
54
|
+
describe '#dataset' do
|
55
|
+
it 'selects all qualified columns and sorts by pk' do
|
56
|
+
expect(users.dataset).to eql(
|
57
|
+
users.select(*users.columns).order(:users__id).dataset
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
51
61
|
|
52
|
-
|
53
|
-
|
54
|
-
|
62
|
+
describe '#primary_key' do
|
63
|
+
it 'returns :id by default' do
|
64
|
+
expect(users.primary_key).to be(:id)
|
65
|
+
end
|
55
66
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
67
|
+
it 'returns configured primary key from the schema' do
|
68
|
+
conf.relation(:other_users) do
|
69
|
+
schema(:users) do
|
70
|
+
attribute :name, ROM::SQL::Types::String.meta(primary_key: true)
|
71
|
+
end
|
72
|
+
end
|
60
73
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
expect(users.distinct(:name)).to_not eq(users)
|
65
|
-
end
|
66
|
-
end
|
74
|
+
expect(container.relations[:other_users].primary_key).to be(:name)
|
75
|
+
end
|
76
|
+
end
|
67
77
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
end
|
74
|
-
end
|
78
|
+
describe '#sum' do
|
79
|
+
it 'returns a sum' do
|
80
|
+
expect(users.sum(:id)).to eql(3)
|
81
|
+
end
|
82
|
+
end
|
75
83
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
end
|
84
|
+
describe '#min' do
|
85
|
+
it 'returns a min' do
|
86
|
+
expect(users.min(:id)).to eql(1)
|
87
|
+
end
|
88
|
+
end
|
82
89
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
end
|
90
|
+
describe '#max' do
|
91
|
+
it 'delegates to dataset and return value' do
|
92
|
+
expect(users.max(:id)).to eql(2)
|
93
|
+
end
|
94
|
+
end
|
89
95
|
|
90
|
-
|
91
|
-
|
92
|
-
|
96
|
+
describe '#avg' do
|
97
|
+
it 'delegates to dataset and return value' do
|
98
|
+
expect(users.avg(:id)).to eql(1.5)
|
99
|
+
end
|
100
|
+
end
|
93
101
|
|
94
|
-
|
102
|
+
describe '#distinct' do
|
103
|
+
it 'delegates to dataset and returns a new relation' do
|
104
|
+
expect(users.dataset).to receive(:distinct).with(:name).and_call_original
|
105
|
+
expect(users.distinct(:name)).to_not eq(users)
|
106
|
+
end
|
107
|
+
end
|
95
108
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
109
|
+
describe '#exclude' do
|
110
|
+
it 'delegates to dataset and returns a new relation' do
|
111
|
+
expect(users.dataset)
|
112
|
+
.to receive(:exclude).with(name: 'Jane').and_call_original
|
113
|
+
expect(users.exclude(name: 'Jane')).to_not eq(users)
|
114
|
+
end
|
115
|
+
end
|
100
116
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
117
|
+
describe '#invert' do
|
118
|
+
it 'delegates to dataset and returns a new relation' do
|
119
|
+
expect(users.dataset).to receive(:invert).and_call_original
|
120
|
+
expect(users.invert).to_not eq(users)
|
121
|
+
end
|
122
|
+
end
|
107
123
|
|
108
|
-
|
109
|
-
|
110
|
-
|
124
|
+
describe '#map' do
|
125
|
+
it 'yields tuples' do
|
126
|
+
result = users.map { |tuple| tuple[:name] }
|
127
|
+
expect(result).to eql(%w(Jane Joe))
|
128
|
+
end
|
111
129
|
|
112
|
-
|
130
|
+
it 'plucks value' do
|
131
|
+
expect(users.map(:name)).to eql(%w(Jane Joe))
|
132
|
+
end
|
133
|
+
end
|
113
134
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
135
|
+
describe '#inner_join' do
|
136
|
+
it 'joins relations using inner join' do
|
137
|
+
result = users.inner_join(:tasks, user_id: :id).select(:name, :title)
|
138
|
+
|
139
|
+
expect(result.to_a).to eql([
|
140
|
+
{ name: 'Jane', title: "Jane's task" },
|
141
|
+
{ name: 'Joe', title: "Joe's task" }
|
142
|
+
])
|
143
|
+
end
|
144
|
+
|
145
|
+
it 'raises error when column names are ambiguous' do
|
146
|
+
expect {
|
147
|
+
users.inner_join(:tasks, user_id: :id).to_a
|
148
|
+
}.to raise_error(Sequel::DatabaseError, /is ambiguous/)
|
149
|
+
end
|
150
|
+
end
|
120
151
|
|
121
|
-
|
122
|
-
|
123
|
-
|
152
|
+
describe '#left_join' do
|
153
|
+
it 'joins relations using left outer join' do
|
154
|
+
result = users.left_join(:tasks, user_id: :id).select(:name, :title)
|
124
155
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
156
|
+
expect(result.to_a).to match_array([
|
157
|
+
{ name: 'Joe', title: "Joe's task" },
|
158
|
+
{ name: 'Jane', title: "Jane's task" }
|
159
|
+
])
|
160
|
+
end
|
161
|
+
end
|
129
162
|
|
130
|
-
|
131
|
-
|
132
|
-
|
163
|
+
describe '#project' do
|
164
|
+
it 'projects the dataset using new column names' do
|
165
|
+
projected = users.sorted.project(:name)
|
133
166
|
|
134
|
-
|
135
|
-
|
136
|
-
|
167
|
+
expect(projected.header).to match_array([:name])
|
168
|
+
expect(projected.first).to eql(name: 'Jane')
|
169
|
+
end
|
170
|
+
end
|
137
171
|
|
138
|
-
|
139
|
-
|
140
|
-
|
172
|
+
describe '#rename' do
|
173
|
+
it 'projects the dataset using new column names' do
|
174
|
+
renamed = users.sorted.rename(id: :user_id, name: :user_name)
|
141
175
|
|
142
|
-
|
143
|
-
|
176
|
+
expect(renamed.first).to eql(user_id: 1, user_name: 'Jane')
|
177
|
+
end
|
178
|
+
end
|
144
179
|
|
145
|
-
|
146
|
-
|
180
|
+
describe '#prefix' do
|
181
|
+
it 'projects the dataset using new column names' do
|
182
|
+
prefixed = users.sorted.prefix(:user)
|
147
183
|
|
148
|
-
|
149
|
-
|
150
|
-
end
|
184
|
+
expect(prefixed.first).to eql(user_id: 1, user_name: 'Jane')
|
185
|
+
end
|
151
186
|
|
152
|
-
|
153
|
-
|
154
|
-
columns = users.sorted.prefix(:user).qualified_columns
|
187
|
+
it 'uses singularized table name as the default prefix' do
|
188
|
+
prefixed = users.sorted.prefix
|
155
189
|
|
156
|
-
|
157
|
-
|
190
|
+
expect(prefixed.first).to eql(user_id: 1, user_name: 'Jane')
|
191
|
+
end
|
192
|
+
end
|
158
193
|
|
159
|
-
|
160
|
-
|
194
|
+
describe '#qualified_columns' do
|
195
|
+
it 'returns qualified column names' do
|
196
|
+
columns = users.sorted.prefix(:user).qualified_columns
|
161
197
|
|
162
|
-
|
163
|
-
|
164
|
-
end
|
198
|
+
expect(columns).to eql([:users__id___user_id, :users__name___user_name])
|
199
|
+
end
|
165
200
|
|
166
|
-
|
167
|
-
|
168
|
-
expect(users.inspect).to include('dataset')
|
169
|
-
end
|
170
|
-
end
|
201
|
+
it 'returns projected qualified column names' do
|
202
|
+
columns = users.sorted.project(:id).prefix(:user).qualified_columns
|
171
203
|
|
172
|
-
|
173
|
-
|
204
|
+
expect(columns).to eql([:users__id___user_id])
|
205
|
+
end
|
206
|
+
end
|
174
207
|
|
175
|
-
|
176
|
-
|
177
|
-
|
208
|
+
describe '#inspect' do
|
209
|
+
it 'includes dataset' do
|
210
|
+
expect(users.inspect).to include('dataset')
|
211
|
+
end
|
212
|
+
end
|
178
213
|
|
179
|
-
|
180
|
-
|
181
|
-
expect(tasks.unique?(title: 'Task One')).to be(false)
|
182
|
-
end
|
183
|
-
end
|
214
|
+
describe '#unique?' do
|
215
|
+
before { tasks.delete }
|
184
216
|
|
185
|
-
|
186
|
-
|
187
|
-
|
217
|
+
it 'returns true when there is only one tuple matching criteria' do
|
218
|
+
expect(tasks.unique?(title: 'Task One')).to be(true)
|
219
|
+
end
|
188
220
|
|
189
|
-
|
190
|
-
|
221
|
+
it 'returns true when there are more than one tuple matching criteria' do
|
222
|
+
tasks.insert(title: 'Task One')
|
223
|
+
expect(tasks.unique?(title: 'Task One')).to be(false)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
describe '#union' do
|
228
|
+
let(:relation1) { users.where(id: 1).select(:id, :name) }
|
229
|
+
let(:relation2) { users.where(id: 2).select(:id, :name) }
|
191
230
|
|
192
|
-
|
231
|
+
it 'unions two relations and returns a new relation' do
|
232
|
+
result = relation1.union(relation2)
|
233
|
+
|
234
|
+
expect(result.to_a).to match_array([
|
235
|
+
{ id: 1, name: 'Jane' },
|
236
|
+
{ id: 2, name: 'Joe' }
|
237
|
+
])
|
238
|
+
end
|
239
|
+
end
|
193
240
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
241
|
+
describe '#pluck' do
|
242
|
+
it 'returns a list of values from a specific column' do
|
243
|
+
expect(users.pluck(:id)).to eql([1, 2])
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
describe '#by_pk' do
|
248
|
+
it 'restricts a relation by its PK' do
|
249
|
+
expect(users.by_pk(1).to_a).to eql([id: 1, name: 'Jane'])
|
250
|
+
end
|
251
|
+
|
252
|
+
it 'is available as a view' do
|
253
|
+
expect(users.by_pk).to be_curried
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
describe '#fetch' do
|
258
|
+
it 'returns a single tuple identified by the pk' do
|
259
|
+
expect(users.fetch(1)).to eql(id: 1, name: 'Jane')
|
260
|
+
end
|
261
|
+
|
262
|
+
it 'raises when tuple was not found' do
|
263
|
+
expect { users.fetch(535315412) }.to raise_error(ROM::TupleCountMismatchError)
|
264
|
+
end
|
265
|
+
|
266
|
+
it 'raises when more tuples were returned' do
|
267
|
+
expect { users.fetch([1, 2]) }.to raise_error(ROM::TupleCountMismatchError)
|
268
|
+
end
|
269
|
+
end
|
198
270
|
end
|
199
271
|
end
|
200
272
|
end
|
data/spec/unit/schema_spec.rb
CHANGED
@@ -1,22 +1,23 @@
|
|
1
|
-
|
1
|
+
RSpec.describe ROM::SQL::Schema do
|
2
|
+
describe '#primary_key' do
|
3
|
+
it 'returns primary key attributes' do
|
4
|
+
schema = Class.new(ROM::Relation[:sql]).schema do
|
5
|
+
attribute :id, ROM::SQL::Types::Serial
|
6
|
+
end
|
2
7
|
|
3
|
-
|
4
|
-
include_context 'database setup'
|
8
|
+
schema.finalize!
|
5
9
|
|
6
|
-
|
7
|
-
it "infers the schema from the database relations" do
|
8
|
-
configuration.relation(:users)
|
9
|
-
|
10
|
-
expect(container.relations.users.to_a)
|
11
|
-
.to eql(container.gateways[:default][:users].to_a)
|
10
|
+
expect(schema.primary_key).to eql([schema[:id]])
|
12
11
|
end
|
13
|
-
end
|
14
12
|
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
it 'returns empty array when there is no PK defined' do
|
14
|
+
schema = Class.new(ROM::Relation[:sql]).schema do
|
15
|
+
attribute :id, ROM::SQL::Types::Int
|
16
|
+
end
|
17
|
+
|
18
|
+
schema.finalize!
|
18
19
|
|
19
|
-
expect
|
20
|
+
expect(schema.primary_key).to eql([])
|
20
21
|
end
|
21
22
|
end
|
22
23
|
end
|