dbee 1.0.0.pre.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +8 -0
  3. data/.gitignore +6 -0
  4. data/.rubocop.yml +23 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +26 -0
  7. data/CHANGELOG.md +7 -0
  8. data/CODE_OF_CONDUCT.md +74 -0
  9. data/Gemfile +5 -0
  10. data/Guardfile +16 -0
  11. data/LICENSE +7 -0
  12. data/README.md +368 -0
  13. data/Rakefile +15 -0
  14. data/bin/console +18 -0
  15. data/dbee.gemspec +33 -0
  16. data/lib/dbee/base.rb +109 -0
  17. data/lib/dbee/model/constraints/base.rb +36 -0
  18. data/lib/dbee/model/constraints/reference.rb +42 -0
  19. data/lib/dbee/model/constraints/static.rb +40 -0
  20. data/lib/dbee/model/constraints.rb +26 -0
  21. data/lib/dbee/model.rb +79 -0
  22. data/lib/dbee/providers/null_provider.rb +21 -0
  23. data/lib/dbee/providers.rb +10 -0
  24. data/lib/dbee/query/field.rb +41 -0
  25. data/lib/dbee/query/filters/base.rb +39 -0
  26. data/lib/dbee/query/filters/contains.rb +19 -0
  27. data/lib/dbee/query/filters/equals.rb +19 -0
  28. data/lib/dbee/query/filters/greater_than.rb +19 -0
  29. data/lib/dbee/query/filters/greater_than_or_equal_to.rb +19 -0
  30. data/lib/dbee/query/filters/less_than.rb +19 -0
  31. data/lib/dbee/query/filters/less_than_or_equal_to.rb +19 -0
  32. data/lib/dbee/query/filters/not_contain.rb +19 -0
  33. data/lib/dbee/query/filters/not_equals.rb +19 -0
  34. data/lib/dbee/query/filters/not_start_with.rb +19 -0
  35. data/lib/dbee/query/filters/starts_with.rb +19 -0
  36. data/lib/dbee/query/filters.rb +40 -0
  37. data/lib/dbee/query/key_path.rb +54 -0
  38. data/lib/dbee/query/sorter.rb +53 -0
  39. data/lib/dbee/query.rb +45 -0
  40. data/lib/dbee/version.rb +12 -0
  41. data/lib/dbee.rb +28 -0
  42. data/spec/dbee/base_spec.rb +35 -0
  43. data/spec/dbee/model/constraints/base_spec.rb +42 -0
  44. data/spec/dbee/model/constraints/reference_spec.rb +37 -0
  45. data/spec/dbee/model/constraints/static_spec.rb +29 -0
  46. data/spec/dbee/model/constraints_spec.rb +25 -0
  47. data/spec/dbee/model_spec.rb +113 -0
  48. data/spec/dbee/providers/null_provider_spec.rb +22 -0
  49. data/spec/dbee/query/field_spec.rb +42 -0
  50. data/spec/dbee/query/filters/base_spec.rb +42 -0
  51. data/spec/dbee/query/filters_spec.rb +33 -0
  52. data/spec/dbee/query/key_path_spec.rb +35 -0
  53. data/spec/dbee/query/sorter_spec.rb +72 -0
  54. data/spec/dbee/query_spec.rb +94 -0
  55. data/spec/dbee_spec.rb +60 -0
  56. data/spec/fixtures/models.rb +115 -0
  57. data/spec/fixtures/models.yaml +101 -0
  58. data/spec/spec_helper.rb +52 -0
  59. metadata +239 -0
data/lib/dbee/base.rb ADDED
@@ -0,0 +1,109 @@
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
+ # Instead of using the configuration-first approach, you could use this super class for
12
+ # Model declaration.
13
+ class Base
14
+ class << self
15
+ def table(name)
16
+ @table_name = name.to_s
17
+
18
+ self
19
+ end
20
+
21
+ def association(name, opts = {})
22
+ associations_by_name[name.to_s] = opts.merge(name: name)
23
+
24
+ self
25
+ end
26
+
27
+ def to_model(name = nil, constraints = [])
28
+ name = derive_name(name)
29
+ key = [name, constraints]
30
+
31
+ to_models[key] ||= Model.make(model_config(name, constraints))
32
+ end
33
+
34
+ def table_name
35
+ @table_name.to_s
36
+ end
37
+
38
+ def associations_by_name
39
+ @associations_by_name ||= {}
40
+ end
41
+
42
+ def inherited_table_name
43
+ subclasses.each do |subclass|
44
+ return subclass.table_name unless subclass.table_name.empty?
45
+ end
46
+
47
+ ''
48
+ end
49
+
50
+ def inherited_associations_by_name
51
+ reversed_subclasses.each_with_object({}) do |subclass, memo|
52
+ memo.merge!(subclass.associations_by_name)
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def subclasses
59
+ ancestors.select { |a| a < Dbee::Base }
60
+ end
61
+
62
+ def reversed_subclasses
63
+ subclasses.reverse
64
+ end
65
+
66
+ def model_config(name, constraints)
67
+ {
68
+ constraints: constraints,
69
+ models: associations,
70
+ name: name,
71
+ table: derive_table
72
+ }
73
+ end
74
+
75
+ def derive_name(name)
76
+ name.to_s.empty? ? inflected_name : name.to_s
77
+ end
78
+
79
+ def derive_table
80
+ inherited_table = inherited_table_name
81
+
82
+ inherited_table.empty? ? inflected_name : inherited_table
83
+ end
84
+
85
+ def associations
86
+ inherited_associations_by_name.values.each_with_object([]) do |config, memo|
87
+ model_klass = config[:model]
88
+ associated_constraints = config[:constraints]
89
+ name = config[:name]
90
+
91
+ memo << model_klass.to_model(name, associated_constraints)
92
+ end
93
+ end
94
+
95
+ def to_models
96
+ @to_models ||= {}
97
+ end
98
+
99
+ def inflected_name
100
+ name.split('::')
101
+ .last
102
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
103
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
104
+ .tr('-', '_')
105
+ .downcase
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,36 @@
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
+ class Constraints
13
+ # Base class for all constraints.
14
+ class Base
15
+ acts_as_hashable
16
+
17
+ attr_reader :name
18
+
19
+ def initialize(name:)
20
+ raise ArgumentError, 'name is required' if name.to_s.empty?
21
+
22
+ @name = name.to_s
23
+ end
24
+
25
+ def hash
26
+ name.hash
27
+ end
28
+
29
+ def ==(other)
30
+ other.name == name
31
+ end
32
+ alias eql? ==
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,42 @@
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_relative 'base'
11
+
12
+ module Dbee
13
+ class Model
14
+ class Constraints
15
+ # A Reference constraint is a constraint between two data models. In DB terms:
16
+ # the name represents the column name on the child and the parent represents the
17
+ # column name on the parent table.
18
+ class Reference < Base
19
+ attr_reader :parent
20
+
21
+ def initialize(name:, parent:)
22
+ super(name: name)
23
+
24
+ raise ArgumentError, 'parent is required' if parent.to_s.empty?
25
+
26
+ @parent = parent.to_s
27
+
28
+ freeze
29
+ end
30
+
31
+ def hash
32
+ "#{super}#{parent}".hash
33
+ end
34
+
35
+ def ==(other)
36
+ super && other.parent == parent
37
+ end
38
+ alias eql? ==
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,40 @@
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_relative 'base'
11
+
12
+ module Dbee
13
+ class Model
14
+ class Constraints
15
+ # A static constraint is a equality constraint on a child column to a static value.
16
+ # It is usually used in conjunction with a ReferenceConstraint, further giving it more
17
+ # scoping.
18
+ class Static < Base
19
+ attr_reader :value
20
+
21
+ def initialize(name:, value: nil)
22
+ super(name: name)
23
+
24
+ @value = value
25
+
26
+ freeze
27
+ end
28
+
29
+ def hash
30
+ "#{super}#{value}".hash
31
+ end
32
+
33
+ def ==(other)
34
+ super && other.value == value
35
+ end
36
+ alias eql? ==
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,26 @@
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_relative 'constraints/reference'
11
+ require_relative 'constraints/static'
12
+
13
+ module Dbee
14
+ class Model
15
+ # Top-level class that allows for the making of constraints. For example,
16
+ # you can call this as:
17
+ # - Constraints.make(type: :reference, name: :id, parent: some_id)
18
+ # - Constraints.make(type: :static, name: :genre, value: :comedy)
19
+ class Constraints
20
+ acts_as_hashable_factory
21
+
22
+ register 'reference', Reference
23
+ register 'static', Static
24
+ end
25
+ end
26
+ end
data/lib/dbee/model.rb ADDED
@@ -0,0 +1,79 @@
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_relative 'model/constraints'
11
+
12
+ module Dbee
13
+ # In DB terms, a Model is usually a table, but it does not have to be. You can also re-model
14
+ # your DB schema using Dbee::Models.
15
+ class Model
16
+ acts_as_hashable
17
+
18
+ JOIN_CHAR = '.'
19
+
20
+ class ModelNotFound < StandardError; end
21
+
22
+ attr_reader :constraints, :name
23
+
24
+ def initialize(name:, constraints: [], models: [], table: '')
25
+ raise ArgumentError, 'name is required' if name.to_s.empty?
26
+
27
+ @name = name.to_s
28
+ @constraints = Constraints.array(constraints)
29
+ @models_by_name = name_hash(Model.array(models))
30
+ @table = table.to_s
31
+
32
+ freeze
33
+ end
34
+
35
+ def name_hash(array)
36
+ array.map { |a| [a.name, a] }.to_h
37
+ end
38
+
39
+ def table
40
+ @table.to_s.empty? ? name : @table
41
+ end
42
+
43
+ def models
44
+ models_by_name.values
45
+ end
46
+
47
+ def ancestors(parts = [], alias_chain = [], found = {})
48
+ return found if Array(parts).empty?
49
+
50
+ alias_chain = [] if Array(alias_chain).empty?
51
+
52
+ model_name = parts.first
53
+
54
+ model = models_by_name[model_name.to_s]
55
+
56
+ raise ModelNotFound, "Cannot traverse: #{model_name}" unless model
57
+
58
+ new_alias_chain = alias_chain + [model_name]
59
+
60
+ new_alias = new_alias_chain.join(JOIN_CHAR)
61
+
62
+ found[new_alias] = model
63
+
64
+ model.ancestors(parts[1..-1], new_alias_chain, found)
65
+ end
66
+
67
+ def ==(other)
68
+ other.name == name &&
69
+ other.table == table &&
70
+ other.models == models &&
71
+ other.constraints == constraints
72
+ end
73
+ alias eql? ==
74
+
75
+ private
76
+
77
+ attr_reader :models_by_name
78
+ end
79
+ end
@@ -0,0 +1,21 @@
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
+ module Providers
12
+ # Default stand-in provider that ships with Dbee. The main use-case would be to plug in a
13
+ # provider or provide your own implementation. There really is no real-world use
14
+ # of this provider.
15
+ class NullProvider
16
+ def sql(_model, _query)
17
+ 'SELECT NULL'
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,10 @@
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_relative 'providers/null_provider'
@@ -0,0 +1,41 @@
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_relative 'key_path'
11
+
12
+ module Dbee
13
+ class Query
14
+ # This class is an abstraction of the SELECT part of a SQL statement.
15
+ # The key_path is the relative path to the column while the display is the AS part of the
16
+ # SQL SELECT statement.
17
+ class Field
18
+ acts_as_hashable
19
+
20
+ attr_reader :key_path, :display
21
+
22
+ def initialize(key_path:, display: nil)
23
+ raise ArgumentError, 'key_path is required' if key_path.to_s.empty?
24
+
25
+ @key_path = KeyPath.get(key_path)
26
+ @display = (display.to_s.empty? ? key_path : display).to_s
27
+
28
+ freeze
29
+ end
30
+
31
+ def hash
32
+ "#{key_path}#{display}".hash
33
+ end
34
+
35
+ def ==(other)
36
+ other.key_path == key_path && other.display == display
37
+ end
38
+ alias eql? ==
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,39 @@
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 Query
12
+ class Filters
13
+ # Defines the shared implementation for all filters.
14
+ class Base
15
+ acts_as_hashable
16
+
17
+ attr_reader :key_path, :value
18
+
19
+ def initialize(key_path:, value: nil)
20
+ raise ArgumentError, 'key_path is required' if key_path.to_s.empty?
21
+
22
+ @key_path = KeyPath.get(key_path)
23
+ @value = value
24
+
25
+ freeze
26
+ end
27
+
28
+ def hash
29
+ "#{key_path}#{value}".hash
30
+ end
31
+
32
+ def ==(other)
33
+ other.key_path == key_path && other.value == value
34
+ end
35
+ alias eql? ==
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,19 @@
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_relative 'base'
11
+
12
+ module Dbee
13
+ class Query
14
+ class Filters
15
+ # Equivalent to a SQL x LIKE '%value%' statement.
16
+ class Contains < Base; end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
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_relative 'base'
11
+
12
+ module Dbee
13
+ class Query
14
+ class Filters
15
+ # Equivalent to a SQL WHERE x = 'y' statement.
16
+ class Equals < Base; end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
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_relative 'base'
11
+
12
+ module Dbee
13
+ class Query
14
+ class Filters
15
+ # Equivalent to a SQL x > 123 statement.
16
+ class GreaterThan < Base; end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
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_relative 'base'
11
+
12
+ module Dbee
13
+ class Query
14
+ class Filters
15
+ # Equivalent to a SQL x >= 123 statement.
16
+ class GreaterThanOrEqualTo < Base; end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
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_relative 'base'
11
+
12
+ module Dbee
13
+ class Query
14
+ class Filters
15
+ # Equivalent to a SQL x < 123 statement.
16
+ class LessThan < Base; end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
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_relative 'base'
11
+
12
+ module Dbee
13
+ class Query
14
+ class Filters
15
+ # Equivalent to a SQL x <= 123 statement.
16
+ class LessThanOrEqualTo < Base; end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
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_relative 'base'
11
+
12
+ module Dbee
13
+ class Query
14
+ class Filters
15
+ # Equivalent to a SQL x NOT LIKE '%value%' statement.
16
+ class NotContain < Base; end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
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_relative 'base'
11
+
12
+ module Dbee
13
+ class Query
14
+ class Filters
15
+ # Equivalent to a SQL WHERE x != 'y' statement.
16
+ class NotEquals < Base; end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
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_relative 'base'
11
+
12
+ module Dbee
13
+ class Query
14
+ class Filters
15
+ # Equivalent to a SQL x NOT LIKE 'value%' statement.
16
+ class NotStartWith < Base; end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
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_relative 'base'
11
+
12
+ module Dbee
13
+ class Query
14
+ class Filters
15
+ # Equivalent to a SQL x LIKE 'value%' statement.
16
+ class StartsWith < Base; end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,40 @@
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_relative 'filters/contains'
11
+ require_relative 'filters/equals'
12
+ require_relative 'filters/greater_than_or_equal_to'
13
+ require_relative 'filters/greater_than'
14
+ require_relative 'filters/less_than_or_equal_to'
15
+ require_relative 'filters/less_than'
16
+ require_relative 'filters/not_contain'
17
+ require_relative 'filters/not_equals'
18
+ require_relative 'filters/not_start_with'
19
+ require_relative 'filters/starts_with'
20
+
21
+ module Dbee
22
+ class Query
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')
25
+ class Filters
26
+ acts_as_hashable_factory
27
+
28
+ register 'contains', Contains
29
+ register 'equals', Equals
30
+ register 'greater_than_or_equal_to', GreaterThanOrEqualTo
31
+ register 'greater_than', GreaterThan
32
+ register 'less_than_or_equal_to', LessThanOrEqualTo
33
+ register 'less_than', LessThan
34
+ register 'not_contain', NotContain
35
+ register 'not_equals', NotEquals
36
+ register 'not_start_with', NotStartWith
37
+ register 'starts_with', StartsWith
38
+ end
39
+ end
40
+ end