dbee 2.0.2 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +14 -10
- data/.ruby-version +1 -1
- data/.travis.yml +4 -4
- data/CHANGELOG.md +43 -0
- data/README.md +257 -43
- data/dbee.gemspec +17 -6
- data/exe/.gitkeep +0 -0
- data/lib/dbee.rb +10 -10
- data/lib/dbee/base.rb +7 -49
- data/lib/dbee/constant_resolver.rb +34 -0
- data/lib/dbee/dsl/association.rb +8 -19
- data/lib/dbee/dsl_schema_builder.rb +86 -0
- data/lib/dbee/key_chain.rb +11 -0
- data/lib/dbee/model.rb +50 -38
- data/lib/dbee/model/relationships.rb +24 -0
- data/lib/dbee/model/relationships/basic.rb +47 -0
- data/lib/dbee/query.rb +30 -24
- data/lib/dbee/query/field.rb +35 -5
- data/lib/dbee/schema.rb +66 -0
- data/lib/dbee/schema_creator.rb +107 -0
- data/lib/dbee/schema_from_tree_based_model.rb +47 -0
- data/lib/dbee/util/make_keyed_by.rb +50 -0
- data/lib/dbee/version.rb +1 -1
- data/spec/dbee/base_spec.rb +9 -72
- data/spec/dbee/constant_resolver_spec.rb +58 -0
- data/spec/dbee/dsl_schema_builder_spec.rb +106 -0
- data/spec/dbee/key_chain_spec.rb +24 -0
- data/spec/dbee/model/constraints_spec.rb +6 -7
- data/spec/dbee/model_spec.rb +62 -59
- data/spec/dbee/query/field_spec.rb +54 -6
- data/spec/dbee/query/filters_spec.rb +16 -17
- data/spec/dbee/query_spec.rb +55 -62
- data/spec/dbee/schema_creator_spec.rb +163 -0
- data/spec/dbee/schema_from_tree_based_model_spec.rb +31 -0
- data/spec/dbee/schema_spec.rb +62 -0
- data/spec/dbee_spec.rb +17 -37
- data/spec/fixtures/models.yaml +254 -56
- data/spec/spec_helper.rb +7 -0
- metadata +83 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2b8250c307ad60bdb8bc51e19fdb93b2c9eb3db0cccfb5daad0a2e621aab57cb
|
4
|
+
data.tar.gz: db66f66f4a56dd40bbaa347218d0827291c789733b9c3f7086ca416e85a09246
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '0085b06c34404de9b93b728713fd118189a2693920668685e66574c2bd23b8cb2009d0ab4a047dbbc8b4e7c69084a3ec595d1fa80aaf6de06bce023d5fff3d1a'
|
7
|
+
data.tar.gz: a295c963939ce9df84689bd3ab34852df111a7c57e685ac07abdb661498dc269fe85e661fae49b77826e43054030fb09c18fe7da648a1cdc78020024bac15a52
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -1,8 +1,15 @@
|
|
1
|
-
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.5
|
3
|
+
NewCops: enable
|
4
|
+
|
5
|
+
Layout/LineLength:
|
2
6
|
Max: 100
|
3
7
|
|
8
|
+
Metrics/AbcSize:
|
9
|
+
Max: 16
|
10
|
+
|
4
11
|
Metrics/BlockLength:
|
5
|
-
|
12
|
+
IgnoredMethods:
|
6
13
|
- let
|
7
14
|
- it
|
8
15
|
- describe
|
@@ -10,14 +17,11 @@ Metrics/BlockLength:
|
|
10
17
|
- specify
|
11
18
|
- define
|
12
19
|
|
13
|
-
Metrics/
|
14
|
-
|
15
|
-
|
16
|
-
AllCops:
|
17
|
-
TargetRubyVersion: 2.3
|
18
|
-
|
19
|
-
Metrics/AbcSize:
|
20
|
-
Max: 16
|
20
|
+
Metrics/ParameterLists:
|
21
|
+
CountKeywordArgs: false
|
21
22
|
|
22
23
|
Metrics/ClassLength:
|
23
24
|
Max: 125
|
25
|
+
|
26
|
+
Metrics/MethodLength:
|
27
|
+
Max: 25
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.6.
|
1
|
+
2.6.6
|
data/.travis.yml
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
env:
|
2
2
|
global:
|
3
3
|
- CC_TEST_REPORTER_ID=e7db23dad7560a076e48357331b0362db3741b62dbdd537bc30de27fa9cab69b
|
4
|
+
- DISABLE_RSPEC_FOCUS=true
|
4
5
|
language: ruby
|
5
6
|
rvm:
|
6
7
|
# Build on the latest stable of all supported Rubies (https://www.ruby-lang.org/en/downloads/):
|
7
|
-
- 2.
|
8
|
-
- 2.
|
9
|
-
- 2.
|
10
|
-
- 2.6.3
|
8
|
+
- 2.5.8
|
9
|
+
- 2.6.6
|
10
|
+
- 2.7.2
|
11
11
|
cache: bundler
|
12
12
|
before_script:
|
13
13
|
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,46 @@
|
|
1
|
+
# 3.0.0 (March 11th, 2021)
|
2
|
+
|
3
|
+
### Additions
|
4
|
+
|
5
|
+
* Support for graph based models. This paves the way for representing more advanced features, such as sub-queries in a more clear way. Since graph based models are similar to DSL models, the hope is that they will be easier to work with and understand.
|
6
|
+
|
7
|
+
### Breaking Changes
|
8
|
+
|
9
|
+
* The `to_model` method on `Dbee::Base` objects has been removed. Use `to_schema` instead.
|
10
|
+
* The `ancestors!` method on `Dbee::Model` has been removed. Use `Dbee::Schema#expand_query_path` instead.
|
11
|
+
|
12
|
+
# 2.1.1 (July 14th, 2020)
|
13
|
+
|
14
|
+
* Removed guard that ensured a query has at least one field to establish a more rational base-case.
|
15
|
+
|
16
|
+
# 2.1.0 (July 13th, 2020)
|
17
|
+
|
18
|
+
### Additions:
|
19
|
+
|
20
|
+
* Added Dbee::Query::Field#aggregator (such as ave, min, max, sum, etc.)
|
21
|
+
* Added Dbee::Query::Field#filters (allows for doing select column filtering)
|
22
|
+
|
23
|
+
### Changes:
|
24
|
+
|
25
|
+
* Bumped minimum Ruby version to 2.5
|
26
|
+
|
27
|
+
# 2.0.3 (January 7th, 2020)
|
28
|
+
|
29
|
+
### Fixes:
|
30
|
+
|
31
|
+
* Constant resolution will now explicitly set inherit to false when calling `Object#const_defined?` and `Object#const_get`. This should equate to less false positives. For example:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
class A; end
|
35
|
+
|
36
|
+
module B
|
37
|
+
class A; end
|
38
|
+
class C; end
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
If `B::A` is the desired class, it would not be suitable to just declare an association on `A` as that would resolve explicitly to `::A`. It also means C cannot be auto-resolved without prefixing/sharing the namespace with parent association `A`.
|
43
|
+
|
1
44
|
# 2.0.2 (November 7th, 2019)
|
2
45
|
|
3
46
|
### Additions:
|
data/README.md
CHANGED
@@ -20,7 +20,7 @@ Both of these solutions ended up closely coupling our domain data layer to ad-ho
|
|
20
20
|
|
21
21
|
## Installation
|
22
22
|
|
23
|
-
This specific library is the core modeling component of the Dbee framework, but by itself, it not completely usable. You will need to provide a SQL generator which understands how to convert the data and query modeling to actual SQL. This library comes with a stub: Dbee::Providers::NullProvider, while the main reference implementation is split out into its own library: [dbee-active_record](https://github.com/bluemarblepayroll/dbee-active_record). Together these two libraries comprise a complete solution. Refer to the other library for more information on installation.
|
23
|
+
This specific library is the core modeling component of the Dbee framework, but by itself, it is not completely usable. You will need to provide a SQL generator which understands how to convert the data and query modeling to actual SQL. This library comes with a stub: Dbee::Providers::NullProvider, while the main reference implementation is split out into its own library: [dbee-active_record](https://github.com/bluemarblepayroll/dbee-active_record). Together these two libraries comprise a complete solution. Refer to the other library for more information on installation.
|
24
24
|
|
25
25
|
To install through Rubygems:
|
26
26
|
|
@@ -189,49 +189,58 @@ The two code-first examples above should be technically equivalent.
|
|
189
189
|
You can choose to alternatively describe your data model using configuration. The YAML below is equivalent to the Ruby sub-classes above:
|
190
190
|
|
191
191
|
````yaml
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
192
|
+
practice:
|
193
|
+
table: practices
|
194
|
+
relationships:
|
195
|
+
patients:
|
196
|
+
model: patient
|
197
|
+
constraints:
|
198
|
+
- type: reference
|
199
|
+
name: practice_id
|
200
|
+
parent: id
|
201
|
+
patient:
|
202
|
+
table: patients
|
203
|
+
relationships:
|
204
|
+
notes:
|
205
|
+
model: note
|
206
|
+
constraints:
|
207
|
+
- type: reference
|
208
|
+
name: patient_id
|
209
|
+
parent: id
|
210
|
+
work_phone_number:
|
211
|
+
model: phone_number
|
212
|
+
constraints:
|
213
|
+
- type: reference
|
214
|
+
name: patient_id
|
215
|
+
parent: id
|
216
|
+
- type: static
|
217
|
+
name: phone_number_type
|
218
|
+
value: work
|
219
|
+
cell_phone_number:
|
220
|
+
model: phone_number
|
221
|
+
constraints:
|
222
|
+
- type: reference
|
223
|
+
name: patient_id
|
224
|
+
parent: id
|
225
|
+
- type: static
|
226
|
+
name: phone_number_type
|
227
|
+
value: cell
|
228
|
+
fax_phone_number:
|
229
|
+
model: phone_number
|
230
|
+
constraints:
|
231
|
+
- type: reference
|
232
|
+
name: patient_id
|
233
|
+
parent: id
|
234
|
+
- type: static
|
235
|
+
name: phone_number_type
|
236
|
+
value: fax
|
237
|
+
note:
|
238
|
+
table: notes
|
239
|
+
phone_number:
|
240
|
+
table: phones
|
232
241
|
````
|
233
242
|
|
234
|
-
It is up to you to determine which modeling technique to use as both are equivalent. Technically speaking, the code-first DSL is nothing more than syntactic sugar on top of Dbee::Model.
|
243
|
+
It is up to you to determine which modeling technique to use as both are equivalent. Technically speaking, the code-first DSL is nothing more than syntactic sugar on top of `Dbee::Schema` and `Dbee::Model`. Also note that prior to version three of this project, a more hierarchical tree based model configuration was used. See [Tree Based Model Backward Compatibility](#tree-based-model-backward-compatibility) below for more information on this.
|
235
244
|
|
236
245
|
#### Table Partitioning
|
237
246
|
|
@@ -275,6 +284,7 @@ Cats:
|
|
275
284
|
The Query API (Dbee::Query) is a simplified and abstract way to model an SQL query. A Query has the following components:
|
276
285
|
|
277
286
|
* fields (SELECT)
|
287
|
+
* from (FROM)
|
278
288
|
* filters (WHERE)
|
279
289
|
* sorters (ORDER BY)
|
280
290
|
* limit (LIMIT/TAKE)
|
@@ -291,6 +301,7 @@ Get all practices:
|
|
291
301
|
|
292
302
|
````ruby
|
293
303
|
query = {
|
304
|
+
from: 'practice',
|
294
305
|
fields: [
|
295
306
|
{ key_path: 'id' },
|
296
307
|
{ key_path: 'active' },
|
@@ -303,6 +314,7 @@ Get all practices, limit to 10, and sort by name (descending) then id (ascending
|
|
303
314
|
|
304
315
|
````ruby
|
305
316
|
query = {
|
317
|
+
from: 'practice',
|
306
318
|
fields: [
|
307
319
|
{ key_path: 'id' },
|
308
320
|
{ key_path: 'active' },
|
@@ -320,6 +332,7 @@ Get top 5 active practices and patient whose name start with 'Sm':
|
|
320
332
|
|
321
333
|
````ruby
|
322
334
|
query = {
|
335
|
+
from: 'practice',
|
323
336
|
fields: [
|
324
337
|
{ key_path: 'name', display: 'Practice Name' },
|
325
338
|
{ key_path: 'patients.first', display: 'Patient First Name' },
|
@@ -338,6 +351,7 @@ Get practice IDs, patient IDs, names, and cell phone numbers that starts with '5
|
|
338
351
|
|
339
352
|
````ruby
|
340
353
|
query = {
|
354
|
+
from: 'practice',
|
341
355
|
fields: [
|
342
356
|
{ key_path: 'id', display: 'Practice ID #' },
|
343
357
|
{ key_path: 'patients.id', display: 'Patient ID #' },
|
@@ -363,6 +377,22 @@ You execute a Query against a Data Model, using a Provider. The sample provider
|
|
363
377
|
|
364
378
|
Here are some sample executions based off the preceding examples:
|
365
379
|
|
380
|
+
##### Base Case
|
381
|
+
|
382
|
+
If a query has no fields then it is implied you would like all fields on the root table. For example:
|
383
|
+
|
384
|
+
````ruby
|
385
|
+
require 'dbee/providers/active_record_provider'
|
386
|
+
|
387
|
+
class Practice < Dbee::Base; end
|
388
|
+
|
389
|
+
provider = Dbee::Providers::ActiveRecordProvider.new
|
390
|
+
query = { from: 'practice' }
|
391
|
+
sql = Dbee.sql(Practice, query, provider)
|
392
|
+
````
|
393
|
+
|
394
|
+
It equivalent to saying: `SELECT practices.* FROM practices`. This helps to establish a deterministic base-case: it returns the same implicit columns that is independent of sql joins (sorters and/or filters may require sql joins.)
|
395
|
+
|
366
396
|
##### Code-First Execution
|
367
397
|
|
368
398
|
````ruby
|
@@ -373,6 +403,7 @@ class Practice < Dbee::Base; end
|
|
373
403
|
provider = Dbee::Providers::ActiveRecordProvider.new
|
374
404
|
|
375
405
|
query = {
|
406
|
+
from: 'practice',
|
376
407
|
fields: [
|
377
408
|
{ key_path: 'id' },
|
378
409
|
{ key_path: 'active' },
|
@@ -391,10 +422,11 @@ require 'dbee/providers/active_record_provider'
|
|
391
422
|
provider = Dbee::Providers::ActiveRecordProvider.new
|
392
423
|
|
393
424
|
model = {
|
394
|
-
|
425
|
+
practice: { table: 'practices' }
|
395
426
|
}
|
396
427
|
|
397
428
|
query = {
|
429
|
+
from: 'practice',
|
398
430
|
fields: [
|
399
431
|
{ key_path: 'id' },
|
400
432
|
{ key_path: 'active' },
|
@@ -407,7 +439,189 @@ sql = Dbee.sql(model, query, provider)
|
|
407
439
|
|
408
440
|
The above examples showed how to use a plugin provider, see the plugin provider's documentation for more information about its options and use.
|
409
441
|
|
442
|
+
#### Aggregation
|
443
|
+
|
444
|
+
Fields can be configured to use aggregation by setting its `aggregator` attribute. For example, say we wanted to count the number of patients per practice:
|
445
|
+
|
446
|
+
**Data Model**:
|
447
|
+
|
448
|
+
````yaml
|
449
|
+
practice:
|
450
|
+
table: practices
|
451
|
+
relationships:
|
452
|
+
patients:
|
453
|
+
model: patient
|
454
|
+
constraints:
|
455
|
+
- type: reference
|
456
|
+
name: practice_id
|
457
|
+
parent: id
|
458
|
+
patient:
|
459
|
+
table: patients
|
460
|
+
````
|
461
|
+
|
462
|
+
**Query**:
|
463
|
+
|
464
|
+
````ruby
|
465
|
+
query = {
|
466
|
+
from: 'practice',
|
467
|
+
fields: [
|
468
|
+
{
|
469
|
+
key_path: 'id',
|
470
|
+
display: 'Practice ID #',
|
471
|
+
},
|
472
|
+
{
|
473
|
+
key_path: 'name',
|
474
|
+
display: 'Practice Name',
|
475
|
+
},
|
476
|
+
{
|
477
|
+
key_path: 'patients.id',
|
478
|
+
display: 'Total Patients',
|
479
|
+
aggregator: :count
|
480
|
+
},
|
481
|
+
]
|
482
|
+
````
|
483
|
+
|
484
|
+
An example of a materialized result would be something akin to:
|
485
|
+
|
486
|
+
Practice ID # | Practice Name | Total Patients
|
487
|
+
------------- | --------------- | --------------
|
488
|
+
1 | Families Choice | 293
|
489
|
+
2 | Awesome Choice | 2305
|
490
|
+
3 | Best Value | 1200
|
491
|
+
|
492
|
+
A complete list of aggregator values can be found by inspecting the `Dbee::Query::Field::Aggregator` constant.
|
493
|
+
|
494
|
+
#### Field/Column Level Filtering & Pivoting
|
495
|
+
|
496
|
+
Fields can also have filters which provide post-filtering (on the select-level instead of at query-level.) This can be used in conjunction with aggregate functions to provide pivoting. For example:
|
497
|
+
|
498
|
+
**Data/Schema Example**:
|
499
|
+
|
500
|
+
patients:
|
501
|
+
|
502
|
+
id | first | last
|
503
|
+
-- | ----- | -----
|
504
|
+
1 | frank | rizzo
|
505
|
+
|
506
|
+
patient_fields:
|
507
|
+
|
508
|
+
id | patient_id | key | value
|
509
|
+
-- | ---------- | --------------- | -----
|
510
|
+
1 | 1 | dob | 1900-01-01
|
511
|
+
2 | 1 | drivers_license | ABC123
|
512
|
+
|
513
|
+
**Model Configuration**:
|
514
|
+
|
515
|
+
````yaml
|
516
|
+
patients:
|
517
|
+
relationships:
|
518
|
+
- patient_fields:
|
519
|
+
constraints:
|
520
|
+
- type: reference
|
521
|
+
parent: id
|
522
|
+
name: patient_id
|
523
|
+
patient_fields:
|
524
|
+
````
|
525
|
+
|
526
|
+
**Query**:
|
527
|
+
|
528
|
+
````ruby
|
529
|
+
query = {
|
530
|
+
from: 'patients',
|
531
|
+
fields: [
|
532
|
+
{
|
533
|
+
key_path: 'id',
|
534
|
+
display: 'ID #'
|
535
|
+
},
|
536
|
+
{
|
537
|
+
key_path: 'first',
|
538
|
+
display: 'First Name'
|
539
|
+
},
|
540
|
+
{
|
541
|
+
aggregator: :max,
|
542
|
+
key_path: 'patient_fields.value',
|
543
|
+
display: 'Date of Birth',
|
544
|
+
filters: [
|
545
|
+
{
|
546
|
+
key_path: 'patient_fields.key',
|
547
|
+
value: 'dob'
|
548
|
+
}
|
549
|
+
]
|
550
|
+
},
|
551
|
+
{
|
552
|
+
aggregator: :max,
|
553
|
+
key_path: 'patient_fields.value',
|
554
|
+
display: 'Drivers License #',
|
555
|
+
filters: [
|
556
|
+
{
|
557
|
+
key_path: 'patient_fields.key',
|
558
|
+
value: 'drivers_license'
|
559
|
+
}
|
560
|
+
]
|
561
|
+
}
|
562
|
+
}
|
563
|
+
}
|
564
|
+
````
|
565
|
+
|
566
|
+
Executing the query above against the data and model would yield:
|
567
|
+
|
568
|
+
ID # | First Name | Date of Birth | Drivers License #
|
569
|
+
-- | ---------- | ------------- | -----------------
|
570
|
+
1 | frank | 1900-01-01 | ABC123
|
571
|
+
|
572
|
+
## Tree Based Model Backward Compatibility
|
573
|
+
|
574
|
+
In version three of this gem, the representation of configuration based models was changed to be more of a graph structure than the previous tree structure. For backwards compatibility, it is still possible to pass this older tree based structure as the first argument `Dbee.sql`. The practices example would be represented this way in the old structure:
|
575
|
+
|
576
|
+
````yaml
|
577
|
+
# Deprecated tree based model configuration:
|
578
|
+
name: practice
|
579
|
+
table: practices
|
580
|
+
models:
|
581
|
+
- name: patients
|
582
|
+
constraints:
|
583
|
+
- type: reference
|
584
|
+
name: practice_id
|
585
|
+
parent: id
|
586
|
+
models:
|
587
|
+
- name: notes
|
588
|
+
constraints:
|
589
|
+
- type: reference
|
590
|
+
name: patient_id
|
591
|
+
parent: id
|
592
|
+
- name: work_phone_number
|
593
|
+
table: phones
|
594
|
+
constraints:
|
595
|
+
- type: reference
|
596
|
+
name: patient_id
|
597
|
+
parent: id
|
598
|
+
- type: static
|
599
|
+
name: phone_number_type
|
600
|
+
value: work
|
601
|
+
- name: cell_phone_number
|
602
|
+
table: phones
|
603
|
+
constraints:
|
604
|
+
- type: reference
|
605
|
+
name: patient_id
|
606
|
+
parent: id
|
607
|
+
- type: static
|
608
|
+
name: phone_number_type
|
609
|
+
value: cell
|
610
|
+
- name: fax_phone_number
|
611
|
+
table: phones
|
612
|
+
constraints:
|
613
|
+
- type: reference
|
614
|
+
name: patient_id
|
615
|
+
parent: id
|
616
|
+
- type: static
|
617
|
+
name: phone_number_type
|
618
|
+
value: fax
|
619
|
+
````
|
410
620
|
|
621
|
+
Also note to further maintain backwards compatibility, queries issued against
|
622
|
+
tree based models do not need the "from" attribute to be defined. This is
|
623
|
+
because the from/starting point of the query can be inferred as the model at
|
624
|
+
the root of the tree.
|
411
625
|
## Contributing
|
412
626
|
|
413
627
|
### Development Environment Configuration
|