dbee 2.0.2 → 3.0.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/.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
|