dbee 2.1.0.pre.alpha → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require 'spec_helper'
11
+ require 'fixtures/models'
12
+
13
+ describe Dbee::SchemaCreator do
14
+ let(:model_hash_graph) do
15
+ {
16
+ 'model1' => {
17
+ relationships: {
18
+ model2: {
19
+ constraints: [
20
+ {
21
+ type: 'reference',
22
+ parent: 'id',
23
+ name: 'model1_id'
24
+ }
25
+ ]
26
+ }
27
+ }
28
+ },
29
+ 'model2' => nil
30
+ }
31
+ end
32
+ let(:schema) { Dbee::Schema.new(model_hash_graph) }
33
+
34
+ let(:model_hash_tree) do
35
+ {
36
+ name: 'model1',
37
+ models: [
38
+ {
39
+ name: 'model2',
40
+ constraints: [
41
+ {
42
+ type: 'reference',
43
+ parent: 'id',
44
+ name: 'model1_id'
45
+ }
46
+ ]
47
+ }
48
+ ]
49
+ }
50
+ end
51
+
52
+ let(:model_tree) { Dbee::Model.make(model_hash_tree) }
53
+
54
+ let(:query_hash) do
55
+ {
56
+ from: 'model1',
57
+ fields: [
58
+ { key_path: :a }
59
+ ]
60
+ }
61
+ end
62
+ let(:query) { Dbee::Query.make(query_hash) }
63
+
64
+ let(:query_hash_no_from) { query_hash.slice(:fields) }
65
+ let(:query_no_from) { Dbee::Query.make(query_hash_no_from) }
66
+
67
+ describe 'query parsing' do
68
+ it 'passes through Dbee::Query instances' do
69
+ expect(described_class.new(schema, query).query).to eq query
70
+ end
71
+
72
+ it 'creates a Dbee::Query from a query hash' do
73
+ expect(described_class.new(schema, query_hash).query).to eq query
74
+ end
75
+
76
+ describe 'the "from" field' do
77
+ it 'raises an error when nil' do
78
+ expect do
79
+ described_class.new(schema, query_hash_no_from)
80
+ end.to raise_error(ArgumentError, 'query requires a from model name')
81
+ end
82
+
83
+ it 'raises an error when the empty string' do
84
+ expect do
85
+ described_class.new(schema, query_hash_no_from.merge(from: ''))
86
+ end.to raise_error(ArgumentError, 'query requires a from model name')
87
+ end
88
+ end
89
+ end
90
+
91
+ describe 'tree based models' do
92
+ it 'creates a schema from a Dbee::Model' do
93
+ expect(described_class.new(model_tree, query).schema).to eq schema
94
+ end
95
+
96
+ describe 'hash detection' do
97
+ it 'creates a schema from a hash model' do
98
+ expect(described_class.new(model_hash_tree, query).schema).to eq schema
99
+ end
100
+
101
+ it 'creates a schema from a minimal hash model with no child models' do
102
+ minimal_tree_hash = { name: :practice }
103
+ minimal_schema = Dbee::Schema.new(practice: nil)
104
+
105
+ expect(described_class.new(minimal_tree_hash, {}).schema).to eq minimal_schema
106
+ end
107
+ end
108
+
109
+ describe 'query "from" field' do
110
+ it 'is set using name of the root model of the tree' do
111
+ expect(described_class.new(model_tree, query_no_from).query).to eq query
112
+ end
113
+
114
+ it 'raises an error the query "from" does not equal the root model name' do
115
+ query_hash_different_from = query_hash_no_from.merge(from: :bogus_model)
116
+
117
+ expect do
118
+ described_class.new(model_tree, query_hash_different_from)
119
+ end.to raise_error(
120
+ ArgumentError,
121
+ "expected from model to be 'model1' but got 'bogus_model'"
122
+ )
123
+ end
124
+ end
125
+ end
126
+
127
+ describe 'graph based models' do
128
+ it 'creates a schema from a hash schema' do
129
+ expect(described_class.new(model_hash_graph, query).schema).to eq schema
130
+ end
131
+
132
+ it 'passes through Dbee::Schema instances' do
133
+ expect(described_class.new(schema, query).schema).to eq schema
134
+ end
135
+
136
+ describe 'when given a Dbee::Base class' do
137
+ let(:dsl_model_class) { PartitionerExamples::Dog }
138
+
139
+ it 'creates a schema' do
140
+ expected_config = yaml_fixture('models.yaml')['Partitioner Example 1']
141
+ expected_schema = Dbee::Schema.new(expected_config)
142
+ dog_query = Dbee::Query.make(query_hash_no_from.merge(from: :dog))
143
+
144
+ expect(described_class.new(dsl_model_class, dog_query).schema).to eq expected_schema
145
+ end
146
+
147
+ it "can infer the query's 'from' field" do
148
+ expect(described_class.new(dsl_model_class, query_no_from).query.from).to eq 'dog'
149
+ end
150
+
151
+ it 'raises an error the query "from" does not equal the DSL model name' do
152
+ cat_query = Dbee::Query.make(query_hash_no_from.merge(from: :cat))
153
+
154
+ expect do
155
+ described_class.new(dsl_model_class, cat_query)
156
+ end.to raise_error(
157
+ ArgumentError,
158
+ "expected from model to be 'dog' but got 'cat'"
159
+ )
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require 'spec_helper'
11
+ require 'fixtures/models'
12
+
13
+ describe Dbee::SchemaFromTreeBasedModel do
14
+ let(:tree_config) do
15
+ Dbee::Model.make(yaml_fixture('models.yaml')['Theaters, Members, and Movies Tree Based'])
16
+ end
17
+ let(:schema_config) do
18
+ yaml_fixture('models.yaml')['Theaters, Members, and Movies from Tree']
19
+ end
20
+
21
+ it 'converts the theaters model' do
22
+ expected_schema = Dbee::Schema.new(schema_config)
23
+ expect(described_class.convert(tree_config)).to eq expected_schema
24
+ end
25
+
26
+ it 'does not raise any errors when converting the readme model' do
27
+ tree_config = Dbee::Model.make(yaml_fixture('models.yaml')['Readme Tree Based'])
28
+
29
+ expect { described_class.convert(tree_config) }.not_to raise_error
30
+ end
31
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require 'spec_helper'
11
+ require_relative '../fixtures/models'
12
+
13
+ describe Dbee::Schema do
14
+ def make_model(model_name)
15
+ raise "no model named '#{model_name}'" unless schema_config.key?(model_name)
16
+
17
+ Dbee::Model.make((schema_config[model_name] || {}).merge('name' => model_name))
18
+ end
19
+
20
+ let(:model_name) do
21
+ 'Theaters, Members, and Movies from DSL'
22
+ end
23
+ let(:schema_config) { yaml_fixture('models.yaml')[model_name] }
24
+
25
+ let(:demographics_model) { make_model('demographic') }
26
+ let(:members_model) { make_model('member') }
27
+ let(:movies_model) { make_model('movie') }
28
+ let(:phone_numbers_model) { make_model('phone_number') }
29
+ let(:theaters_model) { make_model('theater') }
30
+
31
+ let(:subject) { described_class.new(schema_config) }
32
+
33
+ describe '#expand_query_path' do
34
+ specify 'one model case' do
35
+ expect(subject.expand_query_path(members_model, Dbee::KeyPath.new('id'))).to eq []
36
+ end
37
+
38
+ specify 'two model case' do
39
+ expected_path = [[members_model.relationship_for_name('movies'), movies_model]]
40
+ expect(
41
+ subject.expand_query_path(members_model, Dbee::KeyPath.new('movies.id'))
42
+ ).to eq expected_path
43
+ end
44
+
45
+ it 'traverses aliased models' do
46
+ expected_path = [
47
+ [members_model.relationship_for_name('demos'), demographics_model],
48
+ [demographics_model.relationship_for_name('phone_numbers'), phone_numbers_model]
49
+ ]
50
+
51
+ expect(
52
+ subject.expand_query_path(members_model, Dbee::KeyPath.new('demos.phone_numbers.id'))
53
+ ).to eq expected_path
54
+ end
55
+
56
+ it 'raises an error given an unknown relationship' do
57
+ expect do
58
+ subject.expand_query_path(theaters_model, Dbee::KeyPath.new('demographics.id'))
59
+ end.to raise_error("model 'theater' does not have a 'demographics' relationship")
60
+ end
61
+ end
62
+ end
data/spec/dbee_spec.rb CHANGED
@@ -13,59 +13,39 @@ require 'fixtures/models'
13
13
  describe Dbee do
14
14
  describe '#sql' do
15
15
  let(:provider) { Dbee::Providers::NullProvider.new }
16
-
17
- let(:model_hash) do
18
- {
19
- name: 'something'
20
- }
21
- end
22
-
23
- let(:model) { Dbee::Model.make(model_hash) }
24
-
25
16
  let(:query_hash) do
26
17
  {
18
+ from: 'my_model',
27
19
  fields: [
28
20
  { key_path: :a }
29
21
  ]
30
22
  }
31
23
  end
32
-
33
24
  let(:query) { Dbee::Query.make(query_hash) }
25
+ let(:schema) { Dbee::Schema.new({}) }
34
26
 
35
- it 'accepts a hash as a model and passes a Model instance to provider#sql' do
36
- expect(provider).to receive(:sql).with(model, query)
37
-
38
- described_class.sql(model_hash, query, provider)
39
- end
40
-
41
- it 'accepts a Dbee::Model instance as a model and passes a Model instance to provider#sql' do
42
- expect(provider).to receive(:sql).with(model, query)
27
+ it 'accepts a query hash and a Schema and passes them into provider#sql' do
28
+ expect(provider).to receive(:sql).with(schema, query)
43
29
 
44
- described_class.sql(model, query, provider)
30
+ described_class.sql(schema, query_hash, provider)
45
31
  end
46
32
 
47
- it 'accepts a Dbee::Base constant as a model and passes a Model instance to provider#sql' do
48
- model_constant = Models::Theater
49
-
50
- expect(provider).to receive(:sql).with(model_constant.to_model(query.key_chain), query)
51
-
52
- described_class.sql(model_constant, query, provider)
33
+ it 'does not allow a nil schema' do
34
+ expect do
35
+ described_class.sql(nil, query, provider)
36
+ end.to raise_error ArgumentError, /schema or model is required/
53
37
  end
54
38
 
55
- it 'accepts a Dbee::Query instance as a query and passes a Query instance to provider#sql' do
56
- model = Models::Theater.to_model(query.key_chain)
57
-
58
- expect(provider).to receive(:sql).with(model, query)
59
-
60
- described_class.sql(model, query, provider)
39
+ it 'does not allow a nil query' do
40
+ expect do
41
+ described_class.sql(schema, nil, provider)
42
+ end.to raise_error ArgumentError, /query is required/
61
43
  end
62
44
 
63
- it 'accepts a hash as a query and passes a Query instance to provider#sql' do
64
- model = Models::Theater.to_model(query.key_chain)
65
-
66
- expect(provider).to receive(:sql).with(model, query)
67
-
68
- described_class.sql(model, query_hash, provider)
45
+ it 'does not allow a nil provider' do
46
+ expect do
47
+ described_class.sql(schema, query, nil)
48
+ end.to raise_error ArgumentError, /provider is required/
69
49
  end
70
50
  end
71
51
  end
@@ -1,4 +1,4 @@
1
- Theaters, Members, and Movies:
1
+ Theaters, Members, and Movies Tree Based:
2
2
  name: theater
3
3
  table: theaters
4
4
  models:
@@ -119,7 +119,217 @@ Theaters, Members, and Movies:
119
119
  - type: static
120
120
  name: genre
121
121
  value: comedy
122
+
123
+ # Note that since converting from the tree does not have as much context as
124
+ # converting from the DSL, the resulting graph is a bit different.
125
+ Theaters, Members, and Movies from Tree:
126
+ theater:
127
+ table: theaters
128
+ relationships:
129
+ members:
130
+ constraints:
131
+ - type: reference
132
+ parent: id
133
+ name: tid
134
+ - type: reference
135
+ parent: partition
136
+ name: partition
137
+ parent_theater:
138
+ constraints:
139
+ - type: reference
140
+ name: id
141
+ parent: parent_theater_id
142
+ members:
143
+ relationships:
144
+ demos:
145
+ constraints:
146
+ - type: reference
147
+ parent: id
148
+ name: member_id
149
+ movies:
150
+ constraints:
151
+ - type: reference
152
+ parent: id
153
+ name: member_id
154
+ favorite_comic_movies:
155
+ constraints:
156
+ - type: reference
157
+ parent: id
158
+ name: member_id
159
+ - type: static
160
+ name: genre
161
+ value: comic
162
+ favorite_mystery_movies:
163
+ constraints:
164
+ - type: reference
165
+ parent: id
166
+ name: member_id
167
+ - type: static
168
+ name: genre
169
+ value: mystery
170
+ favorite_comedy_movies:
171
+ constraints:
172
+ - type: reference
173
+ parent: id
174
+ name: member_id
175
+ - type: static
176
+ name: genre
177
+ value: comedy
178
+ demos:
179
+ table: demographics
180
+ relationships:
181
+ phone_numbers:
182
+ constraints:
183
+ - type: reference
184
+ parent: id
185
+ name: demographic_id
186
+ phone_numbers:
187
+ movies:
188
+ favorite_comic_movies:
189
+ table: movies
190
+ favorite_mystery_movies:
191
+ table: movies
192
+ favorite_comedy_movies:
193
+ table: movies
194
+ parent_theater:
195
+ table: theaters
196
+ relationships:
197
+ members:
198
+ constraints:
199
+ - type: reference
200
+ parent: id
201
+ name: tid
202
+ - type: reference
203
+ parent: partition
204
+ name: partition
205
+
206
+ Theaters, Members, and Movies from DSL:
207
+ theater:
208
+ table: theaters
209
+ relationships:
210
+ members:
211
+ model: member
212
+ constraints:
213
+ - type: reference
214
+ parent: id
215
+ name: tid
216
+ - type: reference
217
+ parent: partition
218
+ name: partition
219
+ parent_theater:
220
+ model: theater
221
+ constraints:
222
+ - type: reference
223
+ name: id
224
+ parent: parent_theater_id
225
+ member:
226
+ table: members
227
+ relationships:
228
+ movies:
229
+ model: movie
230
+ constraints:
231
+ - type: reference
232
+ parent: id
233
+ name: member_id
234
+ demos:
235
+ model: demographic
236
+ constraints:
237
+ - type: reference
238
+ parent: id
239
+ name: member_id
240
+ favorite_comic_movies:
241
+ model: movie
242
+ constraints:
243
+ - type: reference
244
+ parent: id
245
+ name: member_id
246
+ - type: static
247
+ name: genre
248
+ value: comic
249
+ favorite_mystery_movies:
250
+ model: movie
251
+ constraints:
252
+ - type: reference
253
+ parent: id
254
+ name: member_id
255
+ - type: static
256
+ name: genre
257
+ value: mystery
258
+ favorite_comedy_movies:
259
+ model: movie
260
+ constraints:
261
+ - type: reference
262
+ parent: id
263
+ name: member_id
264
+ - type: static
265
+ name: genre
266
+ value: comedy
267
+ demographic:
268
+ table: demographics
269
+ relationships:
270
+ phone_numbers:
271
+ model: phone_number
272
+ constraints:
273
+ - type: reference
274
+ parent: id
275
+ name: demographic_id
276
+ phone_number:
277
+ table: phone_numbers
278
+ movie:
279
+ table: movies
280
+
122
281
  Readme:
282
+ practice:
283
+ table: practices
284
+ relationships:
285
+ patients:
286
+ model: patient
287
+ constraints:
288
+ - type: reference
289
+ name: practice_id
290
+ parent: id
291
+ patient:
292
+ table: patients
293
+ relationships:
294
+ notes:
295
+ model: note
296
+ constraints:
297
+ - type: reference
298
+ name: patient_id
299
+ parent: id
300
+ work_phone_number:
301
+ model: phone_number
302
+ constraints:
303
+ - type: reference
304
+ name: patient_id
305
+ parent: id
306
+ - type: static
307
+ name: phone_number_type
308
+ value: work
309
+ cell_phone_number:
310
+ model: phone_number
311
+ constraints:
312
+ - type: reference
313
+ name: patient_id
314
+ parent: id
315
+ - type: static
316
+ name: phone_number_type
317
+ value: cell
318
+ fax_phone_number:
319
+ model: phone_number
320
+ constraints:
321
+ - type: reference
322
+ name: patient_id
323
+ parent: id
324
+ - type: static
325
+ name: phone_number_type
326
+ value: fax
327
+ note:
328
+ table: notes
329
+ phone_number:
330
+ table: phones
331
+
332
+ Readme Tree Based:
123
333
  name: practice
124
334
  table: practices
125
335
  models:
@@ -161,63 +371,51 @@ Readme:
161
371
  - type: static
162
372
  name: phone_number_type
163
373
  value: fax
374
+
164
375
  Cycle Example:
165
- name: a
166
- table: as
167
- models:
168
- - name: b1
169
- table: bs
170
- models:
171
- - name: c
172
- table: cs
173
- models:
174
- - name: a
175
- table: as
176
- - name: d
177
- table: ds
178
- models:
179
- - name: a
180
- table: as
181
- - name: b2
182
- table: bs
183
- models:
184
- - name: c
185
- table: cs
186
- models:
187
- - name: a
188
- table: as
189
- - name: d
190
- table: ds
191
- models:
192
- - name: a
193
- table: as
194
- models:
195
- - name: b1
196
- table: bs
197
- models:
198
- - name: c
199
- table: cs
376
+ a:
377
+ table: as
378
+ relationships:
379
+ b1:
380
+ model: b
381
+ b2:
382
+ model: b
383
+ b:
384
+ table: bs
385
+ relationships:
386
+ c:
387
+ d:
388
+ c:
389
+ table: cs
390
+ relationships:
391
+ a:
392
+ d:
393
+ table: ds
394
+ relationships:
395
+ a:
200
396
 
201
397
  Partitioner Example 1:
202
- name: dog
203
- table: animals
204
- partitioners:
205
- - name: type
206
- value: Dog
207
- - name: deleted
208
- value: false
398
+ dog:
399
+ table: animals
400
+ partitioners:
401
+ - name: type
402
+ value: Dog
403
+ - name: deleted
404
+ value: false
209
405
 
210
406
  Partitioner Example 2:
211
- name: owner
212
- table: owners
213
- models:
214
- - name: dogs
215
- table: animals
216
- constraints:
217
- - name: owner_id
218
- parent: id
219
- partitioners:
220
- - name: type
221
- value: Dog
222
- - name: deleted
223
- value: false
407
+ owner:
408
+ table: owners
409
+ relationships:
410
+ dogs:
411
+ model: dog
412
+ constraints:
413
+ - name: owner_id
414
+ parent: id
415
+ dog:
416
+ table: animals
417
+ partitioners:
418
+ - name: type
419
+ value: Dog
420
+ - name: deleted
421
+ value: false
data/spec/spec_helper.rb CHANGED
@@ -9,6 +9,13 @@
9
9
 
10
10
  require 'yaml'
11
11
  require 'pry'
12
+ require 'pry-byebug'
13
+
14
+ RSpec.configure do |config|
15
+ # Allow for disabling auto focus mode in certain environments like CI to
16
+ # prevent false positives when only a subset of the suite passes.
17
+ config.filter_run_when_matching :focus unless ENV['DISABLE_RSPEC_FOCUS'] == 'true'
18
+ end
12
19
 
13
20
  unless ENV['DISABLE_SIMPLECOV'] == 'true'
14
21
  require 'simplecov'