dbee 2.0.2 → 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 +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +14 -10
- data/.ruby-version +1 -1
- data/.travis.yml +4 -4
- data/CHANGELOG.md +43 -0
- data/README.md +257 -43
- data/dbee.gemspec +17 -6
- data/exe/.gitkeep +0 -0
- data/lib/dbee.rb +10 -10
- data/lib/dbee/base.rb +7 -49
- data/lib/dbee/constant_resolver.rb +34 -0
- data/lib/dbee/dsl/association.rb +8 -19
- 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 +30 -24
- data/lib/dbee/query/field.rb +35 -5
- 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 +58 -0
- 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/field_spec.rb +54 -6
- data/spec/dbee/query/filters_spec.rb +16 -17
- data/spec/dbee/query_spec.rb +55 -62
- 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 +83 -18
data/lib/dbee/version.rb
CHANGED
data/spec/dbee/base_spec.rb
CHANGED
@@ -11,52 +11,17 @@ require 'spec_helper'
|
|
11
11
|
require 'fixtures/models'
|
12
12
|
|
13
13
|
describe Dbee::Base do
|
14
|
-
describe '
|
15
|
-
it '
|
16
|
-
|
17
|
-
expected_config = yaml_fixture('models.yaml')[model_name]
|
14
|
+
describe '.to_schema' do
|
15
|
+
it 'passes the keychain and model to DslSchemaBuilder' do
|
16
|
+
key_chain = Dbee::KeyChain.new
|
18
17
|
|
19
|
-
|
18
|
+
schema_builder_double = double(Dbee::DslSchemaBuilder)
|
19
|
+
expect(Dbee::DslSchemaBuilder).to receive(:new).with(Models::A, key_chain).and_return(
|
20
|
+
schema_builder_double
|
21
|
+
)
|
22
|
+
expect(schema_builder_double).to receive(:to_schema)
|
20
23
|
|
21
|
-
|
22
|
-
members.demos.phone_numbers.a
|
23
|
-
members.movies.b
|
24
|
-
members.favorite_comic_movies.c
|
25
|
-
members.favorite_mystery_movies.d
|
26
|
-
members.favorite_comedy_movies.e
|
27
|
-
parent_theater.members.demos.phone_numbers.f
|
28
|
-
parent_theater.members.movies.g
|
29
|
-
parent_theater.members.favorite_comic_movies.h
|
30
|
-
parent_theater.members.favorite_mystery_movies.i
|
31
|
-
parent_theater.members.favorite_comedy_movies.j
|
32
|
-
]
|
33
|
-
|
34
|
-
key_chain = Dbee::KeyChain.new(key_paths)
|
35
|
-
|
36
|
-
actual_model = Models::Theater.to_model(key_chain)
|
37
|
-
|
38
|
-
expect(actual_model).to eq(expected_model)
|
39
|
-
end
|
40
|
-
|
41
|
-
it 'honors key_chain to flatten cyclic references' do
|
42
|
-
model_name = 'Cycle Example'
|
43
|
-
expected_config = yaml_fixture('models.yaml')[model_name]
|
44
|
-
|
45
|
-
expected_model = Dbee::Model.make(expected_config)
|
46
|
-
|
47
|
-
key_paths = %w[
|
48
|
-
b1.c.a.z
|
49
|
-
b1.d.a.y
|
50
|
-
b2.c.a.x
|
51
|
-
b2.d.a.w
|
52
|
-
b2.d.a.b1.c.z
|
53
|
-
]
|
54
|
-
|
55
|
-
key_chain = Dbee::KeyChain.new(key_paths)
|
56
|
-
|
57
|
-
actual_model = Cycles::A.to_model(key_chain)
|
58
|
-
|
59
|
-
expect(actual_model).to eq(expected_model)
|
24
|
+
Models::A.to_schema(key_chain)
|
60
25
|
end
|
61
26
|
end
|
62
27
|
|
@@ -72,32 +37,4 @@ describe Dbee::Base do
|
|
72
37
|
expect(Models::E.inherited_table_name).to eq('table_set_to_e')
|
73
38
|
end
|
74
39
|
end
|
75
|
-
|
76
|
-
describe 'partitioners' do
|
77
|
-
it 'honors partitioners on a root model' do
|
78
|
-
model_name = 'Partitioner Example 1'
|
79
|
-
expected_config = yaml_fixture('models.yaml')[model_name]
|
80
|
-
expected_model = Dbee::Model.make(expected_config)
|
81
|
-
|
82
|
-
key_paths = %w[id]
|
83
|
-
key_chain = Dbee::KeyChain.new(key_paths)
|
84
|
-
|
85
|
-
actual_model = PartitionerExamples::Dog.to_model(key_chain)
|
86
|
-
|
87
|
-
expect(actual_model).to eq(expected_model)
|
88
|
-
end
|
89
|
-
|
90
|
-
it 'honors partitioners on a child model' do
|
91
|
-
model_name = 'Partitioner Example 2'
|
92
|
-
expected_config = yaml_fixture('models.yaml')[model_name]
|
93
|
-
expected_model = Dbee::Model.make(expected_config)
|
94
|
-
|
95
|
-
key_paths = %w[id dogs.id]
|
96
|
-
key_chain = Dbee::KeyChain.new(key_paths)
|
97
|
-
|
98
|
-
actual_model = PartitionerExamples::Owner.to_model(key_chain)
|
99
|
-
|
100
|
-
expect(actual_model).to eq(expected_model)
|
101
|
-
end
|
102
|
-
end
|
103
40
|
end
|
@@ -0,0 +1,58 @@
|
|
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
|
+
|
12
|
+
# rubocop:disable Lint/EmptyClass
|
13
|
+
# These are intentionally empty to keep the tests focused.
|
14
|
+
module A
|
15
|
+
class B; end
|
16
|
+
|
17
|
+
class E; end
|
18
|
+
end
|
19
|
+
|
20
|
+
class B; end
|
21
|
+
|
22
|
+
module C
|
23
|
+
class D; end
|
24
|
+
|
25
|
+
class E; end
|
26
|
+
|
27
|
+
module F
|
28
|
+
class G; end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
# rubocop:enable Lint/EmptyClass
|
32
|
+
|
33
|
+
describe Dbee::ConstantResolver do
|
34
|
+
it 'resolves nested constant with the same as an ancestor constants sibling' do
|
35
|
+
expect(subject.constantize('A::B')).to eq(::A::B)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'resolves root constant' do
|
39
|
+
expect(subject.constantize('B')).to eq(::B)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'does not resolve constant in a different parent module' do
|
43
|
+
expect { subject.constantize('C::B') }.to raise_error(NameError)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'does not resolve constant in a different parent module' do
|
47
|
+
expect { subject.constantize('D') }.to raise_error(NameError)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'resolves same leaf constants specific to their parents' do
|
51
|
+
expect(subject.constantize('A::E')).to eq(::A::E)
|
52
|
+
expect(subject.constantize('C::E')).to eq(::C::E)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'does not resolve constant without its parent' do
|
56
|
+
expect { subject.constantize('F') }.to raise_error(NameError)
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,106 @@
|
|
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::DslSchemaBuilder do
|
14
|
+
specify 'theaters example' do
|
15
|
+
model_name = 'Theaters, Members, and Movies from DSL'
|
16
|
+
expected_config = yaml_fixture('models.yaml')[model_name]
|
17
|
+
|
18
|
+
expected_schema = Dbee::Schema.new(expected_config)
|
19
|
+
|
20
|
+
key_paths = %w[
|
21
|
+
members.a
|
22
|
+
members.demos.phone_numbers.a
|
23
|
+
members.movies.b
|
24
|
+
members.favorite_comic_movies.c
|
25
|
+
members.favorite_mystery_movies.d
|
26
|
+
members.favorite_comedy_movies.e
|
27
|
+
parent_theater.members.demos.phone_numbers.f
|
28
|
+
parent_theater.members.movies.g
|
29
|
+
parent_theater.members.favorite_comic_movies.h
|
30
|
+
parent_theater.members.favorite_mystery_movies.i
|
31
|
+
parent_theater.members.favorite_comedy_movies.j
|
32
|
+
]
|
33
|
+
|
34
|
+
key_chain = Dbee::KeyChain.new(key_paths)
|
35
|
+
|
36
|
+
actual_schema = Models::Theater.to_schema(key_chain)
|
37
|
+
expect(actual_schema).to eq(expected_schema)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'honors key_chain to flatten cyclic references' do
|
41
|
+
model_name = 'Cycle Example'
|
42
|
+
expected_config = yaml_fixture('models.yaml')[model_name]
|
43
|
+
|
44
|
+
expected_schema = Dbee::Schema.new(expected_config)
|
45
|
+
|
46
|
+
key_paths = %w[
|
47
|
+
b1.c.a.z
|
48
|
+
b1.d.a.y
|
49
|
+
b2.c.a.x
|
50
|
+
b2.d.a.w
|
51
|
+
b2.d.a.b1.c.z
|
52
|
+
]
|
53
|
+
|
54
|
+
key_chain = Dbee::KeyChain.new(key_paths)
|
55
|
+
|
56
|
+
actual_schema = Cycles::A.to_schema(key_chain)
|
57
|
+
expect(actual_schema).to eq(expected_schema)
|
58
|
+
end
|
59
|
+
|
60
|
+
describe 'partitioners' do
|
61
|
+
it 'honors partitioners on a root model' do
|
62
|
+
model_name = 'Partitioner Example 1'
|
63
|
+
expected_config = yaml_fixture('models.yaml')[model_name]
|
64
|
+
expected_schema = Dbee::Schema.new(expected_config)
|
65
|
+
|
66
|
+
key_paths = %w[id]
|
67
|
+
key_chain = Dbee::KeyChain.new(key_paths)
|
68
|
+
|
69
|
+
actual_schema = PartitionerExamples::Dog.to_schema(key_chain)
|
70
|
+
|
71
|
+
expect(actual_schema).to eq(expected_schema)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'honors partitioners on a child model' do
|
75
|
+
model_name = 'Partitioner Example 2'
|
76
|
+
expected_config = yaml_fixture('models.yaml')[model_name]
|
77
|
+
expected_schema = Dbee::Schema.new(expected_config)
|
78
|
+
|
79
|
+
key_paths = %w[id dogs.id]
|
80
|
+
key_chain = Dbee::KeyChain.new(key_paths)
|
81
|
+
|
82
|
+
actual_schema = PartitionerExamples::Owner.to_schema(key_chain)
|
83
|
+
|
84
|
+
expect(actual_schema).to eq(expected_schema)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'README examples' do
|
89
|
+
specify 'code-first and configuration-first models are equal' do
|
90
|
+
config = yaml_fixture('models.yaml')['Readme']
|
91
|
+
config_schema = Dbee::Schema.new(config)
|
92
|
+
|
93
|
+
key_chain = Dbee::KeyChain.new(%w[
|
94
|
+
patients.a
|
95
|
+
patients.notes.b
|
96
|
+
patients.work_phone_number.c
|
97
|
+
patients.cell_phone_number.d
|
98
|
+
patients.fax_phone_number.e
|
99
|
+
])
|
100
|
+
|
101
|
+
code_schema = ReadmeDataModels::Practice.to_schema(key_chain)
|
102
|
+
|
103
|
+
expect(config_schema).to eq(code_schema)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
data/spec/dbee/key_chain_spec.rb
CHANGED
@@ -78,4 +78,28 @@ describe Dbee::KeyChain do
|
|
78
78
|
end
|
79
79
|
end
|
80
80
|
end
|
81
|
+
|
82
|
+
describe '#to_unique_ancestors collapses the KeyChain' do
|
83
|
+
let(:collapsed_key_chain) do
|
84
|
+
described_class.new([
|
85
|
+
'z.b.c.d.any_column',
|
86
|
+
'b.c.any_column',
|
87
|
+
'any_column',
|
88
|
+
'44.55.any_column'
|
89
|
+
])
|
90
|
+
end
|
91
|
+
|
92
|
+
specify 'the empty case' do
|
93
|
+
expect(described_class.new.to_unique_ancestors).to eq described_class.new
|
94
|
+
end
|
95
|
+
|
96
|
+
specify 'when there are no ancestors, one key path is returned' do
|
97
|
+
no_query_depth = described_class.new(%w[column1 column2 column3])
|
98
|
+
expect(no_query_depth.to_unique_ancestors).to eq described_class.new(['any_column'])
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'returns a new KeyChain which has one KeyPath per ancestor' do
|
102
|
+
expect(subject.to_unique_ancestors).to eq collapsed_key_chain
|
103
|
+
end
|
104
|
+
end
|
81
105
|
end
|
@@ -9,14 +9,13 @@
|
|
9
9
|
|
10
10
|
require 'spec_helper'
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
Dbee::Model::Constraints::Static => CONSTRAINT_CONFIG.merge(value: :b, type: :static)
|
18
|
-
}.freeze
|
12
|
+
CONSTRAINT_CONFIG = { name: :a }.freeze
|
13
|
+
CONSTRAINT_FACTORIES = {
|
14
|
+
Dbee::Model::Constraints::Reference => CONSTRAINT_CONFIG.merge(parent: :b, type: :reference),
|
15
|
+
Dbee::Model::Constraints::Static => CONSTRAINT_CONFIG.merge(value: :b, type: :static)
|
16
|
+
}.freeze
|
19
17
|
|
18
|
+
describe Dbee::Model::Constraints do
|
20
19
|
CONSTRAINT_FACTORIES.each_pair do |constant, config|
|
21
20
|
it "should instantiate #{constant} objects" do
|
22
21
|
expect(described_class.make(config)).to be_a(constant)
|
data/spec/dbee/model_spec.rb
CHANGED
@@ -55,77 +55,80 @@ describe Dbee::Model do
|
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
-
describe '
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
subject { described_class.make(entity_hash) }
|
64
|
-
|
65
|
-
specify 'returns proper models' do
|
66
|
-
members = subject.models.first
|
67
|
-
|
68
|
-
expected_plan = {
|
69
|
-
%w[members] => members
|
58
|
+
describe '.make_keyed_by' do
|
59
|
+
it 'returns a hash of Models where the keys equal the names of the models' do
|
60
|
+
input = {
|
61
|
+
model1: nil,
|
62
|
+
model2: { table: :model2_table }
|
70
63
|
}
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
expect(plan).to eq(expected_plan)
|
75
|
-
end
|
76
|
-
|
77
|
-
specify 'returns proper multi-level models' do
|
78
|
-
members = subject.models.first
|
79
|
-
demos = members.models.first
|
80
|
-
phone_numbers = demos.models.first
|
81
|
-
|
82
|
-
expected_plan = {
|
83
|
-
%w[members] => members,
|
84
|
-
%w[members demos] => demos,
|
85
|
-
%w[members demos phone_numbers] => phone_numbers
|
64
|
+
expected_result = {
|
65
|
+
'model1' => described_class.new(name: 'model1'),
|
66
|
+
'model2' => described_class.new(name: 'model2', table: 'model2_table')
|
86
67
|
}
|
87
68
|
|
88
|
-
|
89
|
-
|
90
|
-
expect(plan).to eq(expected_plan)
|
69
|
+
expect(described_class.make_keyed_by(:name, input)).to eq expected_result
|
91
70
|
end
|
92
|
-
end
|
93
|
-
|
94
|
-
describe 'equality' do
|
95
|
-
let(:config) { yaml_fixture('models.yaml')['Theaters, Members, and Movies'] }
|
96
71
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
72
|
+
it 'accepts values of Dbee::Model instances' do
|
73
|
+
input = { model1: described_class.new(name: :model1) }
|
74
|
+
expected_result = { 'model1' => described_class.new(name: :model1) }
|
75
|
+
expect(described_class.make_keyed_by(:name, input)).to eq expected_result
|
76
|
+
end
|
102
77
|
|
103
|
-
|
104
|
-
|
78
|
+
it 'accepts values of Dbee::Model instances when the key attribute is a string' do
|
79
|
+
input = { 'model1' => described_class.new(name: 'model1') }
|
80
|
+
expected_result = { 'model1' => described_class.new(name: 'model1') }
|
81
|
+
expect(described_class.make_keyed_by(:name, input)).to eq expected_result
|
105
82
|
end
|
106
83
|
|
107
|
-
it '
|
108
|
-
|
109
|
-
expect
|
84
|
+
it 'raises an error when the input hash key is not equal to the name of the model' do
|
85
|
+
input = { model1: described_class.new(name: :bogus) }
|
86
|
+
expect do
|
87
|
+
described_class.make_keyed_by(:name, input)
|
88
|
+
end.to raise_error ArgumentError, "expected a name of 'model1' but got 'bogus'"
|
110
89
|
end
|
111
90
|
end
|
112
91
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
key_chain = Dbee::KeyChain.new(%w[
|
119
|
-
patients.a
|
120
|
-
patients.notes.b
|
121
|
-
patients.work_phone_number.c
|
122
|
-
patients.cell_phone_number.d
|
123
|
-
patients.fax_phone_number.e
|
124
|
-
])
|
125
|
-
|
126
|
-
code_model = ReadmeDataModels::Practice.to_model(key_chain)
|
92
|
+
describe '#to_s' do
|
93
|
+
it 'is represented by the model name' do
|
94
|
+
expect(described_class.new(name: 'foo').to_s).to eq 'foo'
|
95
|
+
end
|
96
|
+
end
|
127
97
|
|
128
|
-
|
98
|
+
equality_cases = {
|
99
|
+
tree_based: yaml_fixture('models.yaml')['Theaters, Members, and Movies Tree Based'],
|
100
|
+
graph_based: yaml_fixture('models.yaml')['Theaters, Members, and Movies from DSL']['theater']
|
101
|
+
}
|
102
|
+
equality_cases[:graph_based].merge!(name: 'theaters')
|
103
|
+
equality_cases.each do |test_case, config|
|
104
|
+
describe "equality for #{test_case}" do
|
105
|
+
let(:model1) { described_class.make(config) }
|
106
|
+
let(:model2) { described_class.make(config) }
|
107
|
+
|
108
|
+
subject { described_class.make(config) }
|
109
|
+
|
110
|
+
specify 'equality compares attributes' do
|
111
|
+
expect(model1).to eq(model2)
|
112
|
+
expect(model1).to eql(model2)
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'returns false unless comparing same object types' do
|
116
|
+
expect(subject).not_to eq(config)
|
117
|
+
expect(subject).not_to eq(nil)
|
118
|
+
end
|
119
|
+
|
120
|
+
describe 'hash codes' do
|
121
|
+
specify 'are equal when objects are equal' do
|
122
|
+
expect(model1).to eq(model2)
|
123
|
+
expect(model1.hash).to eq(model2.hash)
|
124
|
+
end
|
125
|
+
|
126
|
+
specify 'are not equal when objects are not equal' do
|
127
|
+
different_model = described_class.new(name: :oddball)
|
128
|
+
expect(model1).not_to eq(different_model)
|
129
|
+
expect(model1.hash).not_to eq(different_model.hash)
|
130
|
+
end
|
131
|
+
end
|
129
132
|
end
|
130
133
|
end
|
131
134
|
end
|