dbee 2.1.1 → 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 +4 -1
- data/.travis.yml +2 -1
- data/CHANGELOG.md +11 -0
- data/README.md +133 -58
- data/dbee.gemspec +5 -2
- data/lib/dbee.rb +9 -10
- data/lib/dbee/base.rb +7 -49
- 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 +4 -0
- data/lib/dbee/query/field.rb +1 -6
- 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 +17 -12
- 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/filters_spec.rb +16 -17
- data/spec/dbee/query_spec.rb +55 -54
- 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 +66 -9
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
@@ -9,7 +9,7 @@ Metrics/AbcSize:
|
|
9
9
|
Max: 16
|
10
10
|
|
11
11
|
Metrics/BlockLength:
|
12
|
-
|
12
|
+
IgnoredMethods:
|
13
13
|
- let
|
14
14
|
- it
|
15
15
|
- describe
|
@@ -17,6 +17,9 @@ Metrics/BlockLength:
|
|
17
17
|
- specify
|
18
18
|
- define
|
19
19
|
|
20
|
+
Metrics/ParameterLists:
|
21
|
+
CountKeywordArgs: false
|
22
|
+
|
20
23
|
Metrics/ClassLength:
|
21
24
|
Max: 125
|
22
25
|
|
data/.travis.yml
CHANGED
@@ -1,12 +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
8
|
- 2.5.8
|
8
9
|
- 2.6.6
|
9
|
-
- 2.7.
|
10
|
+
- 2.7.2
|
10
11
|
cache: bundler
|
11
12
|
before_script:
|
12
13
|
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
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
|
+
|
1
12
|
# 2.1.1 (July 14th, 2020)
|
2
13
|
|
3
14
|
* Removed guard that ensured a query has at least one field to establish a more rational base-case.
|
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 #' },
|
@@ -373,7 +387,7 @@ require 'dbee/providers/active_record_provider'
|
|
373
387
|
class Practice < Dbee::Base; end
|
374
388
|
|
375
389
|
provider = Dbee::Providers::ActiveRecordProvider.new
|
376
|
-
query = {}
|
390
|
+
query = { from: 'practice' }
|
377
391
|
sql = Dbee.sql(Practice, query, provider)
|
378
392
|
````
|
379
393
|
|
@@ -389,6 +403,7 @@ class Practice < Dbee::Base; end
|
|
389
403
|
provider = Dbee::Providers::ActiveRecordProvider.new
|
390
404
|
|
391
405
|
query = {
|
406
|
+
from: 'practice',
|
392
407
|
fields: [
|
393
408
|
{ key_path: 'id' },
|
394
409
|
{ key_path: 'active' },
|
@@ -407,10 +422,11 @@ require 'dbee/providers/active_record_provider'
|
|
407
422
|
provider = Dbee::Providers::ActiveRecordProvider.new
|
408
423
|
|
409
424
|
model = {
|
410
|
-
|
425
|
+
practice: { table: 'practices' }
|
411
426
|
}
|
412
427
|
|
413
428
|
query = {
|
429
|
+
from: 'practice',
|
414
430
|
fields: [
|
415
431
|
{ key_path: 'id' },
|
416
432
|
{ key_path: 'active' },
|
@@ -430,19 +446,24 @@ Fields can be configured to use aggregation by setting its `aggregator` attribut
|
|
430
446
|
**Data Model**:
|
431
447
|
|
432
448
|
````yaml
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
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
|
440
460
|
````
|
441
461
|
|
442
462
|
**Query**:
|
443
463
|
|
444
464
|
````ruby
|
445
465
|
query = {
|
466
|
+
from: 'practice',
|
446
467
|
fields: [
|
447
468
|
{
|
448
469
|
key_path: 'id',
|
@@ -492,19 +513,21 @@ id | patient_id | key | value
|
|
492
513
|
**Model Configuration**:
|
493
514
|
|
494
515
|
````yaml
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
516
|
+
patients:
|
517
|
+
relationships:
|
518
|
+
- patient_fields:
|
519
|
+
constraints:
|
520
|
+
- type: reference
|
521
|
+
parent: id
|
522
|
+
name: patient_id
|
523
|
+
patient_fields:
|
502
524
|
````
|
503
525
|
|
504
526
|
**Query**:
|
505
527
|
|
506
528
|
````ruby
|
507
529
|
query = {
|
530
|
+
from: 'patients',
|
508
531
|
fields: [
|
509
532
|
{
|
510
533
|
key_path: 'id',
|
@@ -546,7 +569,59 @@ ID # | First Name | Date of Birth | Drivers License #
|
|
546
569
|
-- | ---------- | ------------- | -----------------
|
547
570
|
1 | frank | 1900-01-01 | ABC123
|
548
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
|
+
````
|
549
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.
|
550
625
|
## Contributing
|
551
626
|
|
552
627
|
### Development Environment Configuration
|
data/dbee.gemspec
CHANGED
@@ -34,9 +34,12 @@ Gem::Specification.new do |s|
|
|
34
34
|
|
35
35
|
s.add_development_dependency('guard-rspec', '~>4.7')
|
36
36
|
s.add_development_dependency('pry', '~>0')
|
37
|
+
s.add_development_dependency('pry-byebug')
|
37
38
|
s.add_development_dependency('rake', '~> 13')
|
38
39
|
s.add_development_dependency('rspec')
|
39
|
-
s.add_development_dependency('rubocop', '~>
|
40
|
-
s.add_development_dependency('
|
40
|
+
s.add_development_dependency('rubocop', '~> 1')
|
41
|
+
s.add_development_dependency('rubocop-rake')
|
42
|
+
s.add_development_dependency('rubocop-rspec')
|
43
|
+
s.add_development_dependency('simplecov', '~>0.19.0')
|
41
44
|
s.add_development_dependency('simplecov-console', '~>0.7.0')
|
42
45
|
end
|
data/lib/dbee.rb
CHANGED
@@ -13,11 +13,15 @@ require 'forwardable'
|
|
13
13
|
|
14
14
|
require_relative 'dbee/base'
|
15
15
|
require_relative 'dbee/constant_resolver'
|
16
|
+
require_relative 'dbee/dsl_schema_builder'
|
16
17
|
require_relative 'dbee/key_chain'
|
17
18
|
require_relative 'dbee/key_path'
|
18
19
|
require_relative 'dbee/model'
|
19
|
-
require_relative 'dbee/query'
|
20
20
|
require_relative 'dbee/providers'
|
21
|
+
require_relative 'dbee/query'
|
22
|
+
require_relative 'dbee/schema'
|
23
|
+
require_relative 'dbee/schema_creator'
|
24
|
+
require_relative 'dbee/schema_from_tree_based_model'
|
21
25
|
|
22
26
|
# Top-level namespace that provides the main public API.
|
23
27
|
module Dbee
|
@@ -31,16 +35,11 @@ module Dbee
|
|
31
35
|
@inflector ||= Dry::Inflector.new
|
32
36
|
end
|
33
37
|
|
34
|
-
def sql(
|
35
|
-
|
36
|
-
model =
|
37
|
-
if model.is_a?(Hash) || model.is_a?(Model)
|
38
|
-
Model.make(model)
|
39
|
-
else
|
40
|
-
model.to_model(query.key_chain)
|
41
|
-
end
|
38
|
+
def sql(schema_or_model, query_input, provider)
|
39
|
+
raise ArgumentError, 'a provider is required' unless provider
|
42
40
|
|
43
|
-
|
41
|
+
schema_compat = SchemaCreator.new(schema_or_model, query_input)
|
42
|
+
provider.sql(schema_compat.schema, schema_compat.query)
|
44
43
|
end
|
45
44
|
end
|
46
45
|
end
|
data/lib/dbee/base.rb
CHANGED
@@ -7,8 +7,8 @@
|
|
7
7
|
# LICENSE file in the root directory of this source tree.
|
8
8
|
#
|
9
9
|
|
10
|
-
require_relative 'dsl/association'
|
11
10
|
require_relative 'dsl/association_builder'
|
11
|
+
require_relative 'dsl/association'
|
12
12
|
require_relative 'dsl/methods'
|
13
13
|
require_relative 'dsl/reflectable'
|
14
14
|
|
@@ -22,23 +22,9 @@ module Dbee
|
|
22
22
|
BASE_CLASS_CONSTANT = Dbee::Base
|
23
23
|
|
24
24
|
class << self
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
# of a Query. This is not true for configuration-first Model definitions because, in that
|
29
|
-
# case, cycles do not exist since the nature of the configuration is flat.
|
30
|
-
def to_model(key_chain, name = nil, constraints = [], path_parts = [])
|
31
|
-
derived_name = name.to_s.empty? ? inflected_class_name(self.name) : name.to_s
|
32
|
-
key = [key_chain, derived_name, constraints, path_parts]
|
33
|
-
|
34
|
-
to_models[key] ||= Model.make(
|
35
|
-
model_config(
|
36
|
-
key_chain,
|
37
|
-
derived_name,
|
38
|
-
constraints,
|
39
|
-
path_parts + [name]
|
40
|
-
)
|
41
|
-
)
|
25
|
+
# Returns the smallest needed Dbee::Schema for the provided key_chain.
|
26
|
+
def to_schema(key_chain)
|
27
|
+
DslSchemaBuilder.new(self, key_chain).to_schema
|
42
28
|
end
|
43
29
|
|
44
30
|
def inherited_table_name
|
@@ -58,43 +44,15 @@ module Dbee
|
|
58
44
|
end
|
59
45
|
end
|
60
46
|
|
61
|
-
|
62
|
-
|
63
|
-
def model_config(key_chain, name, constraints, path_parts)
|
64
|
-
{
|
65
|
-
constraints: constraints,
|
66
|
-
models: associations(key_chain, path_parts),
|
67
|
-
name: name,
|
68
|
-
partitioners: inherited_partitioners,
|
69
|
-
table: inherited_table_name
|
70
|
-
}
|
71
|
-
end
|
72
|
-
|
73
|
-
def associations(key_chain, path_parts)
|
74
|
-
inherited_associations.select { |c| key_chain.ancestor_path?(path_parts, c.name) }
|
75
|
-
.map do |association|
|
76
|
-
model_constant = association.model_constant
|
77
|
-
|
78
|
-
model_constant.to_model(
|
79
|
-
key_chain,
|
80
|
-
association.name,
|
81
|
-
association.constraints,
|
82
|
-
path_parts
|
83
|
-
)
|
84
|
-
end
|
47
|
+
def inflected_class_name
|
48
|
+
inflector.underscore(inflector.demodulize(name))
|
85
49
|
end
|
86
50
|
|
87
|
-
|
88
|
-
@to_models ||= {}
|
89
|
-
end
|
51
|
+
private
|
90
52
|
|
91
53
|
def inflected_table_name(name)
|
92
54
|
inflector.pluralize(inflector.underscore(inflector.demodulize(name)))
|
93
55
|
end
|
94
|
-
|
95
|
-
def inflected_class_name(name)
|
96
|
-
inflector.underscore(inflector.demodulize(name))
|
97
|
-
end
|
98
56
|
end
|
99
57
|
end
|
100
58
|
end
|