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