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 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