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