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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a6408442138aafff2ca06cd0161a5fd34a10270a773fb6bd90a9cde54c593b66
4
- data.tar.gz: 8dfb19dc5001ba6e7718cac33e0041eaef49747343a189138dd165f82b825bfb
3
+ metadata.gz: 2b8250c307ad60bdb8bc51e19fdb93b2c9eb3db0cccfb5daad0a2e621aab57cb
4
+ data.tar.gz: db66f66f4a56dd40bbaa347218d0827291c789733b9c3f7086ca416e85a09246
5
5
  SHA512:
6
- metadata.gz: d26d78a27695eb0c4ca15055ada5624c802d4f22c9d7a5fb110b940bcf1bdc56f1bdcb65f0c4754820439bf94fcc6ba029dd1f922c6e53f9a07698a862b5d897
7
- data.tar.gz: 5074fbbc002af0e8e7335a2a1e25bfbaa25923cdbc6171d177de5ebee6147236248f2d7efd86ca4d398ac2abf2b4a4873d49d2cd2a38bfde8cbf46fc6e8bc7b8
6
+ metadata.gz: '0085b06c34404de9b93b728713fd118189a2693920668685e66574c2bd23b8cb2009d0ab4a047dbbc8b4e7c69084a3ec595d1fa80aaf6de06bce023d5fff3d1a'
7
+ data.tar.gz: a295c963939ce9df84689bd3ab34852df111a7c57e685ac07abdb661498dc269fe85e661fae49b77826e43054030fb09c18fe7da648a1cdc78020024bac15a52
data/.gitignore CHANGED
@@ -4,3 +4,4 @@
4
4
  /coverage
5
5
  Gemfile.lock
6
6
  /pkg
7
+ .vscode
data/.rubocop.yml CHANGED
@@ -1,8 +1,15 @@
1
- Metrics/LineLength:
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
- ExcludedMethods:
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/MethodLength:
14
- Max: 25
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.3
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.3.8
8
- - 2.4.6
9
- - 2.5.5
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
- name: practice
193
- models:
194
- - name: patients
195
- constraints:
196
- - type: reference
197
- name: practice_id
198
- parent: id
199
- models:
200
- - name: notes
201
- constraints:
202
- - type: reference
203
- name: patient_id
204
- parent: id
205
- - name: work_phone_number
206
- table: phones
207
- constraints:
208
- - type: reference
209
- name: patient_id
210
- parent: id
211
- - type: static
212
- name: phone_number_type
213
- value: work
214
- - name: cell_phone_number
215
- table: phones
216
- constraints:
217
- - type: reference
218
- name: patient_id
219
- parent: id
220
- - type: static
221
- name: phone_number_type
222
- value: cell
223
- - name: fax_phone_number
224
- table: phones
225
- constraints:
226
- - type: reference
227
- name: patient_id
228
- parent: id
229
- - type: static
230
- name: phone_number_type
231
- value: fax
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
- name: :practice
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