dbee 2.1.1 → 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: b3c9c1444028bdfd5a4aa9eeb8c98055184f1b2cec2b819376e3f5344850d3b9
4
- data.tar.gz: '059ac6adca29bae5a1eb0fc9f13247159047f1e8c82b3b31398d4492913ef595'
3
+ metadata.gz: 2b8250c307ad60bdb8bc51e19fdb93b2c9eb3db0cccfb5daad0a2e621aab57cb
4
+ data.tar.gz: db66f66f4a56dd40bbaa347218d0827291c789733b9c3f7086ca416e85a09246
5
5
  SHA512:
6
- metadata.gz: f67474cd84508029e3f3ca6e91ee2a1192b3cb1f8bf36f6b2d18a9c51e75ba3af897b01ea16676c6c79b000bd8d44d1381a7785fa56a9b81b906d47332e1c687
7
- data.tar.gz: 6d6d061db4ef0c39cc1af32368d725c8354af5fc143a562f10e3beed7c3d260f7ddc93ab2aaff6016c483f370e9ccfec5e54c72dffcb8044c723025cb40dfdb5
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
@@ -9,7 +9,7 @@ Metrics/AbcSize:
9
9
  Max: 16
10
10
 
11
11
  Metrics/BlockLength:
12
- ExcludedMethods:
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.1
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
- 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 #' },
@@ -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
- name: :practice
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
- name: practice
434
- models:
435
- - name: patients
436
- constraints:
437
- - type: reference
438
- name: practice_id
439
- parent: id
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
- name: patients
496
- models:
497
- - name: patient_fields
498
- constraints:
499
- - type: reference
500
- parent: id
501
- name: patient_id
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', '~>0.88.0')
40
- s.add_development_dependency('simplecov', '~>0.18.5')
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(model, query, provider)
35
- query = Query.make(query)
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
- provider.sql(model, query)
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
- # This method is cycle-resistant due to the fact that it is a requirement to send in a
26
- # key_chain. That means each model produced using to_model is specific to a set of desired
27
- # fields. Basically, you cannot derive a Model from a Base subclass without the context
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
- private
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
- def to_models
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