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.
@@ -9,24 +9,23 @@
9
9
 
10
10
  require 'spec_helper'
11
11
 
12
- describe Dbee::Query::Filters do
13
- FILTERS_CONFIG = { key_path: 'a.b.c', value: :d }.freeze
14
-
15
- FILTER_FACTORIES = {
16
- Dbee::Query::Filters::Contains => FILTERS_CONFIG.merge(type: :contains),
17
- Dbee::Query::Filters::Equals => FILTERS_CONFIG.merge(type: :equals),
18
- Dbee::Query::Filters::GreaterThanOrEqualTo => FILTERS_CONFIG.merge(
19
- type: :greater_than_or_equal_to
20
- ),
21
- Dbee::Query::Filters::GreaterThan => FILTERS_CONFIG.merge(type: :greater_than),
22
- Dbee::Query::Filters::LessThanOrEqualTo => FILTERS_CONFIG.merge(type: :less_than_or_equal_to),
23
- Dbee::Query::Filters::LessThan => FILTERS_CONFIG.merge(type: :less_than),
24
- Dbee::Query::Filters::NotContain => FILTERS_CONFIG.merge(type: :not_contain),
25
- Dbee::Query::Filters::NotEquals => FILTERS_CONFIG.merge(type: :not_equals),
26
- Dbee::Query::Filters::NotStartWith => FILTERS_CONFIG.merge(type: :not_start_with),
27
- Dbee::Query::Filters::StartsWith => FILTERS_CONFIG.merge(type: :starts_with)
28
- }.freeze
12
+ FILTERS_CONFIG = { key_path: 'a.b.c', value: :d }.freeze
13
+ FILTER_FACTORIES = {
14
+ Dbee::Query::Filters::Contains => FILTERS_CONFIG.merge(type: :contains),
15
+ Dbee::Query::Filters::Equals => FILTERS_CONFIG.merge(type: :equals),
16
+ Dbee::Query::Filters::GreaterThanOrEqualTo => FILTERS_CONFIG.merge(
17
+ type: :greater_than_or_equal_to
18
+ ),
19
+ Dbee::Query::Filters::GreaterThan => FILTERS_CONFIG.merge(type: :greater_than),
20
+ Dbee::Query::Filters::LessThanOrEqualTo => FILTERS_CONFIG.merge(type: :less_than_or_equal_to),
21
+ Dbee::Query::Filters::LessThan => FILTERS_CONFIG.merge(type: :less_than),
22
+ Dbee::Query::Filters::NotContain => FILTERS_CONFIG.merge(type: :not_contain),
23
+ Dbee::Query::Filters::NotEquals => FILTERS_CONFIG.merge(type: :not_equals),
24
+ Dbee::Query::Filters::NotStartWith => FILTERS_CONFIG.merge(type: :not_start_with),
25
+ Dbee::Query::Filters::StartsWith => FILTERS_CONFIG.merge(type: :starts_with)
26
+ }.freeze
29
27
 
28
+ describe Dbee::Query::Filters do
30
29
  FILTER_FACTORIES.each_pair do |constant, config|
31
30
  it "should instantiate #{constant} objects" do
32
31
  expect(described_class.make(config)).to be_a(constant)
@@ -9,9 +9,63 @@
9
9
 
10
10
  require 'spec_helper'
11
11
 
12
+ README_EXAMPLES = {
13
+ 'Get all practices' => {
14
+ fields: [
15
+ { key_path: 'id' },
16
+ { key_path: 'active' },
17
+ { key_path: 'name' }
18
+ ]
19
+ },
20
+ 'Get all practices, limit to 10, and sort by name (descending) then id (ascending)' => {
21
+ fields: [
22
+ { key_path: 'id' },
23
+ { key_path: 'active' },
24
+ { key_path: 'name' }
25
+ ],
26
+ sorters: [
27
+ { key_path: 'name', direction: :descending },
28
+ { key_path: 'id' }
29
+ ],
30
+ limit: 10
31
+ },
32
+ "Get top 5 active practices and patient whose name start with 'Sm':" => {
33
+ fields: [
34
+ { key_path: 'name', display: 'Practice Name' },
35
+ { key_path: 'patients.first', display: 'Patient First Name' },
36
+ { key_path: 'patients.middle', display: 'Patient Middle Name' },
37
+ { key_path: 'patients.last', display: 'Patient Last Name' }
38
+ ],
39
+ filters: [
40
+ { type: :equals, key_path: 'active', value: true },
41
+ { type: :starts_with, key_path: 'patients.last', value: 'Sm' }
42
+ ],
43
+ limit: 5
44
+ },
45
+ 'Get practice IDs, patient IDs, names, and cell phone numbers that starts with 555' => {
46
+ fields: [
47
+ { key_path: 'id', display: 'Practice ID #' },
48
+ { key_path: 'patients.id', display: 'Patient ID #' },
49
+ { key_path: 'patients.first', display: 'Patient First Name' },
50
+ { key_path: 'patients.middle', display: 'Patient Middle Name' },
51
+ { key_path: 'patients.last', display: 'Patient Last Name' },
52
+ { key_path: 'patients.cell_phone_numbers.phone_number', display: 'Patient Cell #' }
53
+ ],
54
+ filters: [
55
+ { type: :equals, key_path: 'active', value: true },
56
+ {
57
+ type: :starts_with,
58
+ key_path: 'patients.cell_phone_numbers.phone_number',
59
+ value: '555'
60
+ }
61
+ ]
62
+ }
63
+ }.freeze
64
+
12
65
  describe Dbee::Query do
13
66
  let(:config) do
14
67
  {
68
+ from: 'my_model',
15
69
  fields: [
16
70
  { key_path: 'matt.nick.sam', display: 'some display' },
17
71
  { key_path: 'katie' },
@@ -116,60 +170,7 @@ describe Dbee::Query do
116
170
  end
117
171
 
118
172
  context 'README examples' do
119
- EXAMPLES = {
120
- 'Get all practices' => {
121
- fields: [
122
- { key_path: 'id' },
123
- { key_path: 'active' },
124
- { key_path: 'name' }
125
- ]
126
- },
127
- 'Get all practices, limit to 10, and sort by name (descending) then id (ascending)' => {
128
- fields: [
129
- { key_path: 'id' },
130
- { key_path: 'active' },
131
- { key_path: 'name' }
132
- ],
133
- sorters: [
134
- { key_path: 'name', direction: :descending },
135
- { key_path: 'id' }
136
- ],
137
- limit: 10
138
- },
139
- "Get top 5 active practices and patient whose name start with 'Sm':" => {
140
- fields: [
141
- { key_path: 'name', display: 'Practice Name' },
142
- { key_path: 'patients.first', display: 'Patient First Name' },
143
- { key_path: 'patients.middle', display: 'Patient Middle Name' },
144
- { key_path: 'patients.last', display: 'Patient Last Name' }
145
- ],
146
- filters: [
147
- { type: :equals, key_path: 'active', value: true },
148
- { type: :starts_with, key_path: 'patients.last', value: 'Sm' }
149
- ],
150
- limit: 5
151
- },
152
- 'Get practice IDs, patient IDs, names, and cell phone numbers that starts with 555' => {
153
- fields: [
154
- { key_path: 'id', display: 'Practice ID #' },
155
- { key_path: 'patients.id', display: 'Patient ID #' },
156
- { key_path: 'patients.first', display: 'Patient First Name' },
157
- { key_path: 'patients.middle', display: 'Patient Middle Name' },
158
- { key_path: 'patients.last', display: 'Patient Last Name' },
159
- { key_path: 'patients.cell_phone_numbers.phone_number', display: 'Patient Cell #' }
160
- ],
161
- filters: [
162
- { type: :equals, key_path: 'active', value: true },
163
- {
164
- type: :starts_with,
165
- key_path: 'patients.cell_phone_numbers.phone_number',
166
- value: '555'
167
- }
168
- ]
169
- }
170
- }.freeze
171
-
172
- EXAMPLES.each_pair do |name, query|
173
+ README_EXAMPLES.each_pair do |name, query|
173
174
  specify name do
174
175
  expect(described_class.make(query)).to be_a(described_class)
175
176
  end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require 'spec_helper'
11
+ require 'fixtures/models'
12
+
13
+ describe Dbee::SchemaCreator do
14
+ let(:model_hash_graph) do
15
+ {
16
+ 'model1' => {
17
+ relationships: {
18
+ model2: {
19
+ constraints: [
20
+ {
21
+ type: 'reference',
22
+ parent: 'id',
23
+ name: 'model1_id'
24
+ }
25
+ ]
26
+ }
27
+ }
28
+ },
29
+ 'model2' => nil
30
+ }
31
+ end
32
+ let(:schema) { Dbee::Schema.new(model_hash_graph) }
33
+
34
+ let(:model_hash_tree) do
35
+ {
36
+ name: 'model1',
37
+ models: [
38
+ {
39
+ name: 'model2',
40
+ constraints: [
41
+ {
42
+ type: 'reference',
43
+ parent: 'id',
44
+ name: 'model1_id'
45
+ }
46
+ ]
47
+ }
48
+ ]
49
+ }
50
+ end
51
+
52
+ let(:model_tree) { Dbee::Model.make(model_hash_tree) }
53
+
54
+ let(:query_hash) do
55
+ {
56
+ from: 'model1',
57
+ fields: [
58
+ { key_path: :a }
59
+ ]
60
+ }
61
+ end
62
+ let(:query) { Dbee::Query.make(query_hash) }
63
+
64
+ let(:query_hash_no_from) { query_hash.slice(:fields) }
65
+ let(:query_no_from) { Dbee::Query.make(query_hash_no_from) }
66
+
67
+ describe 'query parsing' do
68
+ it 'passes through Dbee::Query instances' do
69
+ expect(described_class.new(schema, query).query).to eq query
70
+ end
71
+
72
+ it 'creates a Dbee::Query from a query hash' do
73
+ expect(described_class.new(schema, query_hash).query).to eq query
74
+ end
75
+
76
+ describe 'the "from" field' do
77
+ it 'raises an error when nil' do
78
+ expect do
79
+ described_class.new(schema, query_hash_no_from)
80
+ end.to raise_error(ArgumentError, 'query requires a from model name')
81
+ end
82
+
83
+ it 'raises an error when the empty string' do
84
+ expect do
85
+ described_class.new(schema, query_hash_no_from.merge(from: ''))
86
+ end.to raise_error(ArgumentError, 'query requires a from model name')
87
+ end
88
+ end
89
+ end
90
+
91
+ describe 'tree based models' do
92
+ it 'creates a schema from a Dbee::Model' do
93
+ expect(described_class.new(model_tree, query).schema).to eq schema
94
+ end
95
+
96
+ describe 'hash detection' do
97
+ it 'creates a schema from a hash model' do
98
+ expect(described_class.new(model_hash_tree, query).schema).to eq schema
99
+ end
100
+
101
+ it 'creates a schema from a minimal hash model with no child models' do
102
+ minimal_tree_hash = { name: :practice }
103
+ minimal_schema = Dbee::Schema.new(practice: nil)
104
+
105
+ expect(described_class.new(minimal_tree_hash, {}).schema).to eq minimal_schema
106
+ end
107
+ end
108
+
109
+ describe 'query "from" field' do
110
+ it 'is set using name of the root model of the tree' do
111
+ expect(described_class.new(model_tree, query_no_from).query).to eq query
112
+ end
113
+
114
+ it 'raises an error the query "from" does not equal the root model name' do
115
+ query_hash_different_from = query_hash_no_from.merge(from: :bogus_model)
116
+
117
+ expect do
118
+ described_class.new(model_tree, query_hash_different_from)
119
+ end.to raise_error(
120
+ ArgumentError,
121
+ "expected from model to be 'model1' but got 'bogus_model'"
122
+ )
123
+ end
124
+ end
125
+ end
126
+
127
+ describe 'graph based models' do
128
+ it 'creates a schema from a hash schema' do
129
+ expect(described_class.new(model_hash_graph, query).schema).to eq schema
130
+ end
131
+
132
+ it 'passes through Dbee::Schema instances' do
133
+ expect(described_class.new(schema, query).schema).to eq schema
134
+ end
135
+
136
+ describe 'when given a Dbee::Base class' do
137
+ let(:dsl_model_class) { PartitionerExamples::Dog }
138
+
139
+ it 'creates a schema' do
140
+ expected_config = yaml_fixture('models.yaml')['Partitioner Example 1']
141
+ expected_schema = Dbee::Schema.new(expected_config)
142
+ dog_query = Dbee::Query.make(query_hash_no_from.merge(from: :dog))
143
+
144
+ expect(described_class.new(dsl_model_class, dog_query).schema).to eq expected_schema
145
+ end
146
+
147
+ it "can infer the query's 'from' field" do
148
+ expect(described_class.new(dsl_model_class, query_no_from).query.from).to eq 'dog'
149
+ end
150
+
151
+ it 'raises an error the query "from" does not equal the DSL model name' do
152
+ cat_query = Dbee::Query.make(query_hash_no_from.merge(from: :cat))
153
+
154
+ expect do
155
+ described_class.new(dsl_model_class, cat_query)
156
+ end.to raise_error(
157
+ ArgumentError,
158
+ "expected from model to be 'dog' but got 'cat'"
159
+ )
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require 'spec_helper'
11
+ require 'fixtures/models'
12
+
13
+ describe Dbee::SchemaFromTreeBasedModel do
14
+ let(:tree_config) do
15
+ Dbee::Model.make(yaml_fixture('models.yaml')['Theaters, Members, and Movies Tree Based'])
16
+ end
17
+ let(:schema_config) do
18
+ yaml_fixture('models.yaml')['Theaters, Members, and Movies from Tree']
19
+ end
20
+
21
+ it 'converts the theaters model' do
22
+ expected_schema = Dbee::Schema.new(schema_config)
23
+ expect(described_class.convert(tree_config)).to eq expected_schema
24
+ end
25
+
26
+ it 'does not raise any errors when converting the readme model' do
27
+ tree_config = Dbee::Model.make(yaml_fixture('models.yaml')['Readme Tree Based'])
28
+
29
+ expect { described_class.convert(tree_config) }.not_to raise_error
30
+ end
31
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Copyright (c) 2019-present, Blue Marble Payroll, LLC
5
+ #
6
+ # This source code is licensed under the MIT license found in the
7
+ # LICENSE file in the root directory of this source tree.
8
+ #
9
+
10
+ require 'spec_helper'
11
+ require_relative '../fixtures/models'
12
+
13
+ describe Dbee::Schema do
14
+ def make_model(model_name)
15
+ raise "no model named '#{model_name}'" unless schema_config.key?(model_name)
16
+
17
+ Dbee::Model.make((schema_config[model_name] || {}).merge('name' => model_name))
18
+ end
19
+
20
+ let(:model_name) do
21
+ 'Theaters, Members, and Movies from DSL'
22
+ end
23
+ let(:schema_config) { yaml_fixture('models.yaml')[model_name] }
24
+
25
+ let(:demographics_model) { make_model('demographic') }
26
+ let(:members_model) { make_model('member') }
27
+ let(:movies_model) { make_model('movie') }
28
+ let(:phone_numbers_model) { make_model('phone_number') }
29
+ let(:theaters_model) { make_model('theater') }
30
+
31
+ let(:subject) { described_class.new(schema_config) }
32
+
33
+ describe '#expand_query_path' do
34
+ specify 'one model case' do
35
+ expect(subject.expand_query_path(members_model, Dbee::KeyPath.new('id'))).to eq []
36
+ end
37
+
38
+ specify 'two model case' do
39
+ expected_path = [[members_model.relationship_for_name('movies'), movies_model]]
40
+ expect(
41
+ subject.expand_query_path(members_model, Dbee::KeyPath.new('movies.id'))
42
+ ).to eq expected_path
43
+ end
44
+
45
+ it 'traverses aliased models' do
46
+ expected_path = [
47
+ [members_model.relationship_for_name('demos'), demographics_model],
48
+ [demographics_model.relationship_for_name('phone_numbers'), phone_numbers_model]
49
+ ]
50
+
51
+ expect(
52
+ subject.expand_query_path(members_model, Dbee::KeyPath.new('demos.phone_numbers.id'))
53
+ ).to eq expected_path
54
+ end
55
+
56
+ it 'raises an error given an unknown relationship' do
57
+ expect do
58
+ subject.expand_query_path(theaters_model, Dbee::KeyPath.new('demographics.id'))
59
+ end.to raise_error("model 'theater' does not have a 'demographics' relationship")
60
+ end
61
+ end
62
+ end
data/spec/dbee_spec.rb CHANGED
@@ -13,59 +13,39 @@ require 'fixtures/models'
13
13
  describe Dbee do
14
14
  describe '#sql' do
15
15
  let(:provider) { Dbee::Providers::NullProvider.new }
16
-
17
- let(:model_hash) do
18
- {
19
- name: 'something'
20
- }
21
- end
22
-
23
- let(:model) { Dbee::Model.make(model_hash) }
24
-
25
16
  let(:query_hash) do
26
17
  {
18
+ from: 'my_model',
27
19
  fields: [
28
20
  { key_path: :a }
29
21
  ]
30
22
  }
31
23
  end
32
-
33
24
  let(:query) { Dbee::Query.make(query_hash) }
25
+ let(:schema) { Dbee::Schema.new({}) }
34
26
 
35
- it 'accepts a hash as a model and passes a Model instance to provider#sql' do
36
- expect(provider).to receive(:sql).with(model, query)
37
-
38
- described_class.sql(model_hash, query, provider)
39
- end
40
-
41
- it 'accepts a Dbee::Model instance as a model and passes a Model instance to provider#sql' do
42
- expect(provider).to receive(:sql).with(model, query)
27
+ it 'accepts a query hash and a Schema and passes them into provider#sql' do
28
+ expect(provider).to receive(:sql).with(schema, query)
43
29
 
44
- described_class.sql(model, query, provider)
30
+ described_class.sql(schema, query_hash, provider)
45
31
  end
46
32
 
47
- it 'accepts a Dbee::Base constant as a model and passes a Model instance to provider#sql' do
48
- model_constant = Models::Theater
49
-
50
- expect(provider).to receive(:sql).with(model_constant.to_model(query.key_chain), query)
51
-
52
- described_class.sql(model_constant, query, provider)
33
+ it 'does not allow a nil schema' do
34
+ expect do
35
+ described_class.sql(nil, query, provider)
36
+ end.to raise_error ArgumentError, /schema or model is required/
53
37
  end
54
38
 
55
- it 'accepts a Dbee::Query instance as a query and passes a Query instance to provider#sql' do
56
- model = Models::Theater.to_model(query.key_chain)
57
-
58
- expect(provider).to receive(:sql).with(model, query)
59
-
60
- described_class.sql(model, query, provider)
39
+ it 'does not allow a nil query' do
40
+ expect do
41
+ described_class.sql(schema, nil, provider)
42
+ end.to raise_error ArgumentError, /query is required/
61
43
  end
62
44
 
63
- it 'accepts a hash as a query and passes a Query instance to provider#sql' do
64
- model = Models::Theater.to_model(query.key_chain)
65
-
66
- expect(provider).to receive(:sql).with(model, query)
67
-
68
- described_class.sql(model, query_hash, provider)
45
+ it 'does not allow a nil provider' do
46
+ expect do
47
+ described_class.sql(schema, query, nil)
48
+ end.to raise_error ArgumentError, /provider is required/
69
49
  end
70
50
  end
71
51
  end