dbee 1.1.0 → 1.2.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/CHANGELOG.md +11 -0
- data/README.md +38 -0
- data/lib/dbee/base.rb +15 -0
- data/lib/dbee/model/partitioner.rb +44 -0
- data/lib/dbee/model.rb +34 -28
- data/lib/dbee/query/filters.rb +1 -1
- data/lib/dbee/version.rb +1 -1
- data/spec/dbee/base_spec.rb +28 -0
- data/spec/dbee/model_spec.rb +6 -6
- data/spec/fixtures/models.rb +17 -0
- data/spec/fixtures/models.yaml +23 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ae136319b1de6f828e34e6e0546b7c6663848efecbf8bfb42f8fe6204bebb20c
|
4
|
+
data.tar.gz: 1690b0f58befeade514c2949163d37ada74f673235ab64a49ec18f7ffde58e1d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82c42cf23dbaed18644da3b2e02bc406833a65ca6429c8d31d824de44cf08fe2b503c4af32c6199646fbe797abe7af9dc434d75acff91b1fb7b502d2f7eb6d35
|
7
|
+
data.tar.gz: '07482747b169ccfde19405381d5128bd07ee269374e75c0f577ee34c26b3a0d877654c70da3a0b1bdd8ce31a1e591a4d0d8c11050390b3a5be18e06f7b312b11'
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
# 1.2.0 (August 29th, 2019)
|
2
|
+
|
3
|
+
Additions:
|
4
|
+
|
5
|
+
* Added partitioners to a model specification. A Partitioner is essentially a way of explicitly specifying that a data model must meet a specific equality for a column.
|
6
|
+
|
7
|
+
Changes:
|
8
|
+
|
9
|
+
* Model#ancestors renamed to Model#ancestors!
|
10
|
+
* Model#ancestor now returns a hash where the key is an array of strings and not just a pre-concatenated string.
|
11
|
+
|
1
12
|
# 1.1.0 (August 28th, 2019)
|
2
13
|
|
3
14
|
Additions:
|
data/README.md
CHANGED
@@ -175,6 +175,44 @@ models:
|
|
175
175
|
|
176
176
|
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.
|
177
177
|
|
178
|
+
#### Table Partitioning
|
179
|
+
|
180
|
+
You can leverage the model partitioners for hard-coding partitioning by column=value. The initial use-case for this was to mirror how ActiveRecord deals with (Single Table Inheritance)[https://api.rubyonrails.org/v6.0.0/classes/ActiveRecord/Base.html#class-ActiveRecord::Base-label-Single+table+inheritance]. Here is a basic example of how to partition an `animals` table for different subclasses:
|
181
|
+
|
182
|
+
##### Code-first:
|
183
|
+
|
184
|
+
````ruby
|
185
|
+
class Dogs < Dbee::Base
|
186
|
+
table 'animals'
|
187
|
+
|
188
|
+
partitioner :type, 'Dog'
|
189
|
+
end
|
190
|
+
|
191
|
+
class Cats < Dbee::Base
|
192
|
+
table 'animals'
|
193
|
+
|
194
|
+
partitioner :type, 'Cat'
|
195
|
+
end
|
196
|
+
````
|
197
|
+
|
198
|
+
##### Configuration-first:
|
199
|
+
|
200
|
+
````yaml
|
201
|
+
Dogs:
|
202
|
+
name: dogs
|
203
|
+
table: animals
|
204
|
+
partitioners:
|
205
|
+
- name: type
|
206
|
+
value: Dog
|
207
|
+
|
208
|
+
Cats:
|
209
|
+
name: cats
|
210
|
+
table: animals
|
211
|
+
partitioners:
|
212
|
+
- name: type
|
213
|
+
value: Cat
|
214
|
+
````
|
215
|
+
|
178
216
|
### The Query API
|
179
217
|
|
180
218
|
The Query API (Dbee::Query) is a simplified and abstract way to model an SQL query. A Query has the following components:
|
data/lib/dbee/base.rb
CHANGED
@@ -12,6 +12,10 @@ module Dbee
|
|
12
12
|
# Model declaration.
|
13
13
|
class Base
|
14
14
|
class << self
|
15
|
+
def partitioner(name, value)
|
16
|
+
partitioners << { name: name, value: value }
|
17
|
+
end
|
18
|
+
|
15
19
|
def table(name)
|
16
20
|
@table_name = name.to_s
|
17
21
|
|
@@ -51,6 +55,10 @@ module Dbee
|
|
51
55
|
@associations_by_name ||= {}
|
52
56
|
end
|
53
57
|
|
58
|
+
def partitioners
|
59
|
+
@partitioners ||= []
|
60
|
+
end
|
61
|
+
|
54
62
|
def table_name?
|
55
63
|
!table_name.empty?
|
56
64
|
end
|
@@ -65,6 +73,12 @@ module Dbee
|
|
65
73
|
end
|
66
74
|
end
|
67
75
|
|
76
|
+
def inherited_partitioners
|
77
|
+
reversed_subclasses.inject([]) do |memo, subclass|
|
78
|
+
memo + subclass.partitioners
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
68
82
|
private
|
69
83
|
|
70
84
|
def subclasses
|
@@ -78,6 +92,7 @@ module Dbee
|
|
78
92
|
def model_config(key_chain, name, constraints, path_parts)
|
79
93
|
{
|
80
94
|
constraints: constraints,
|
95
|
+
partitioners: inherited_partitioners,
|
81
96
|
models: associations(key_chain, path_parts),
|
82
97
|
name: name,
|
83
98
|
table: derive_table
|
@@ -0,0 +1,44 @@
|
|
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
|
+
module Dbee
|
11
|
+
class Model
|
12
|
+
# An Partitioner is a way to explicitly define constraints on a model. For example, say we
|
13
|
+
# want to create a data model, but restrict the returned data to a subset based on a 'type'
|
14
|
+
# column like ActiveRecord does for Single Table Inheritance. We could use a partition
|
15
|
+
# to define this constraint.
|
16
|
+
class Partitioner
|
17
|
+
acts_as_hashable
|
18
|
+
|
19
|
+
attr_reader :name, :value
|
20
|
+
|
21
|
+
def initialize(name: '', value: nil)
|
22
|
+
raise ArgumentError, 'name is required' if name.to_s.empty?
|
23
|
+
|
24
|
+
@name = name.to_s
|
25
|
+
@value = value
|
26
|
+
end
|
27
|
+
|
28
|
+
def <=>(other)
|
29
|
+
"#{name}#{value}" <=> "#{other.name}#{other.value}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def hash
|
33
|
+
"#{name}#{value}".hash
|
34
|
+
end
|
35
|
+
|
36
|
+
def ==(other)
|
37
|
+
other.instance_of?(self.class) &&
|
38
|
+
other.name == name &&
|
39
|
+
other.value == value
|
40
|
+
end
|
41
|
+
alias eql? ==
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/dbee/model.rb
CHANGED
@@ -8,6 +8,7 @@
|
|
8
8
|
#
|
9
9
|
|
10
10
|
require_relative 'model/constraints'
|
11
|
+
require_relative 'model/partitioner'
|
11
12
|
|
12
13
|
module Dbee
|
13
14
|
# In DB terms, a Model is usually a table, but it does not have to be. You can also re-model
|
@@ -16,53 +17,48 @@ module Dbee
|
|
16
17
|
extend Forwardable
|
17
18
|
acts_as_hashable
|
18
19
|
|
19
|
-
JOIN_CHAR = '.'
|
20
|
-
|
21
|
-
private_constant :JOIN_CHAR
|
22
|
-
|
23
20
|
class ModelNotFoundError < StandardError; end
|
24
21
|
|
25
|
-
attr_reader :constraints, :name, :table
|
26
|
-
|
27
|
-
def_delegator :models_by_name, :values, :models
|
22
|
+
attr_reader :constraints, :filters, :name, :partitioners, :table
|
28
23
|
|
29
|
-
def_delegator :
|
24
|
+
def_delegator :models_by_name, :values, :models
|
25
|
+
def_delegator :models, :sort, :sorted_models
|
26
|
+
def_delegator :constraints, :sort, :sorted_constraints
|
27
|
+
def_delegator :partitioners, :sort, :sorted_partitioners
|
30
28
|
|
31
|
-
|
32
|
-
|
33
|
-
def initialize(name:, constraints: [], models: [], table: '')
|
29
|
+
def initialize(name:, constraints: [], models: [], partitioners: [], table: '')
|
34
30
|
raise ArgumentError, 'name is required' if name.to_s.empty?
|
35
31
|
|
36
32
|
@name = name.to_s
|
37
|
-
@constraints = Constraints.array(constraints)
|
33
|
+
@constraints = Constraints.array(constraints).uniq
|
38
34
|
@models_by_name = name_hash(Model.array(models))
|
35
|
+
@partitioners = Partitioner.array(partitioners).uniq
|
39
36
|
@table = table.to_s.empty? ? @name : table.to_s
|
40
37
|
|
41
38
|
freeze
|
42
39
|
end
|
43
40
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
def ancestors(parts = [],
|
41
|
+
# This recursive method will walk a path of model names (parts) and return back a
|
42
|
+
# flattened hash instead of a nested object structure.
|
43
|
+
# The hash key will be an array of strings (model names) and the value will be the
|
44
|
+
# identified model.
|
45
|
+
def ancestors!(parts = [], visited_parts = [], found = {})
|
49
46
|
return found if Array(parts).empty?
|
50
47
|
|
51
|
-
|
52
|
-
|
53
|
-
model_name = parts.first
|
48
|
+
# Take the first entry in parts
|
49
|
+
model_name = parts.first.to_s
|
54
50
|
|
55
|
-
|
51
|
+
# Ensure we have it registered as a child, or raise error
|
52
|
+
model = assert_model(model_name, visited_parts)
|
56
53
|
|
57
|
-
|
54
|
+
# Push onto visited list
|
55
|
+
visited_parts += [model_name]
|
58
56
|
|
59
|
-
|
57
|
+
# Add found model to flattened structure
|
58
|
+
found[visited_parts] = model
|
60
59
|
|
61
|
-
|
62
|
-
|
63
|
-
found[new_alias] = model
|
64
|
-
|
65
|
-
model.ancestors(parts[1..-1], new_alias_chain, found)
|
60
|
+
# Recursively call for next parts in the chain
|
61
|
+
model.ancestors!(parts[1..-1], visited_parts, found)
|
66
62
|
end
|
67
63
|
|
68
64
|
def ==(other)
|
@@ -70,6 +66,7 @@ module Dbee
|
|
70
66
|
other.name == name &&
|
71
67
|
other.table == table &&
|
72
68
|
other.sorted_constraints == sorted_constraints &&
|
69
|
+
other.sorted_partitioners == sorted_partitioners &&
|
73
70
|
other.sorted_models == sorted_models
|
74
71
|
end
|
75
72
|
alias eql? ==
|
@@ -81,5 +78,14 @@ module Dbee
|
|
81
78
|
private
|
82
79
|
|
83
80
|
attr_reader :models_by_name
|
81
|
+
|
82
|
+
def assert_model(model_name, visited_parts)
|
83
|
+
models_by_name[model_name] ||
|
84
|
+
raise(ModelNotFoundError, "Missing: #{model_name}, after: #{visited_parts}")
|
85
|
+
end
|
86
|
+
|
87
|
+
def name_hash(array)
|
88
|
+
array.map { |a| [a.name, a] }.to_h
|
89
|
+
end
|
84
90
|
end
|
85
91
|
end
|
data/lib/dbee/query/filters.rb
CHANGED
@@ -21,7 +21,7 @@ require_relative 'filters/starts_with'
|
|
21
21
|
module Dbee
|
22
22
|
class Query
|
23
23
|
# Top-level class that allows for the making of filters. For example, you can call this as:
|
24
|
-
# - Filters.make(type: :contains, value: 'something')
|
24
|
+
# - Filters.make(key_path: :field, type: :contains, value: 'something')
|
25
25
|
class Filters
|
26
26
|
acts_as_hashable_factory
|
27
27
|
|
data/lib/dbee/version.rb
CHANGED
data/spec/dbee/base_spec.rb
CHANGED
@@ -72,4 +72,32 @@ describe Dbee::Base do
|
|
72
72
|
expect(Models::E.inherited_table_name).to eq('table_set_to_e')
|
73
73
|
end
|
74
74
|
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::Dogs.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::Owners.to_model(key_chain)
|
99
|
+
|
100
|
+
expect(actual_model).to eq(expected_model)
|
101
|
+
end
|
102
|
+
end
|
75
103
|
end
|
data/spec/dbee/model_spec.rb
CHANGED
@@ -66,10 +66,10 @@ describe Dbee::Model do
|
|
66
66
|
members = subject.models.first
|
67
67
|
|
68
68
|
expected_plan = {
|
69
|
-
|
69
|
+
%w[members] => members
|
70
70
|
}
|
71
71
|
|
72
|
-
plan = subject.ancestors(%w[members])
|
72
|
+
plan = subject.ancestors!(%w[members])
|
73
73
|
|
74
74
|
expect(plan).to eq(expected_plan)
|
75
75
|
end
|
@@ -80,12 +80,12 @@ describe Dbee::Model do
|
|
80
80
|
phone_numbers = demos.models.first
|
81
81
|
|
82
82
|
expected_plan = {
|
83
|
-
|
84
|
-
|
85
|
-
|
83
|
+
%w[members] => members,
|
84
|
+
%w[members demos] => demos,
|
85
|
+
%w[members demos phone_numbers] => phone_numbers
|
86
86
|
}
|
87
87
|
|
88
|
-
plan = subject.ancestors(%w[members demos phone_numbers])
|
88
|
+
plan = subject.ancestors!(%w[members demos phone_numbers])
|
89
89
|
|
90
90
|
expect(plan).to eq(expected_plan)
|
91
91
|
end
|
data/spec/fixtures/models.rb
CHANGED
@@ -139,3 +139,20 @@ module Cycles
|
|
139
139
|
association :a, model: 'Cycles::A'
|
140
140
|
end
|
141
141
|
end
|
142
|
+
|
143
|
+
# Examples of Rails Single Table Inheritance.
|
144
|
+
module PartitionerExamples
|
145
|
+
class Owners < Dbee::Base
|
146
|
+
association :dogs, model: 'PartitionerExamples::Dogs', constraints: {
|
147
|
+
name: :owner_id, parent: :id
|
148
|
+
}
|
149
|
+
end
|
150
|
+
|
151
|
+
class Dogs < Dbee::Base
|
152
|
+
table 'animals'
|
153
|
+
|
154
|
+
partitioner :type, 'Dog'
|
155
|
+
|
156
|
+
partitioner :deleted, false
|
157
|
+
end
|
158
|
+
end
|
data/spec/fixtures/models.yaml
CHANGED
@@ -186,3 +186,26 @@ Cycle Example:
|
|
186
186
|
table: b
|
187
187
|
models:
|
188
188
|
- name: c
|
189
|
+
|
190
|
+
Partitioner Example 1:
|
191
|
+
name: dogs
|
192
|
+
table: animals
|
193
|
+
partitioners:
|
194
|
+
- name: type
|
195
|
+
value: Dog
|
196
|
+
- name: deleted
|
197
|
+
value: false
|
198
|
+
|
199
|
+
Partitioner Example 2:
|
200
|
+
name: owners
|
201
|
+
models:
|
202
|
+
- name: dogs
|
203
|
+
table: animals
|
204
|
+
constraints:
|
205
|
+
- name: owner_id
|
206
|
+
parent: id
|
207
|
+
partitioners:
|
208
|
+
- name: type
|
209
|
+
value: Dog
|
210
|
+
- name: deleted
|
211
|
+
value: false
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dbee
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew Ruggio
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-08-
|
11
|
+
date: 2019-08-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: acts_as_hashable
|
@@ -162,6 +162,7 @@ files:
|
|
162
162
|
- lib/dbee/model/constraints/base.rb
|
163
163
|
- lib/dbee/model/constraints/reference.rb
|
164
164
|
- lib/dbee/model/constraints/static.rb
|
165
|
+
- lib/dbee/model/partitioner.rb
|
165
166
|
- lib/dbee/providers.rb
|
166
167
|
- lib/dbee/providers/null_provider.rb
|
167
168
|
- lib/dbee/query.rb
|