dbee 2.1.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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