dbee-active_record 2.0.0 → 2.1.0.pre.alpha
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/.rubocop.yml +24 -9
- data/.ruby-version +1 -1
- data/.travis.yml +3 -7
- data/CHANGELOG.md +30 -0
- data/dbee-active_record.gemspec +14 -6
- data/exe/.gitkeep +0 -0
- data/lib/dbee/providers/active_record_provider/expression_builder.rb +47 -19
- data/lib/dbee/providers/active_record_provider/expression_builder/{constraint_maker.rb → constraint.rb} +11 -11
- data/lib/dbee/providers/active_record_provider/expression_builder/{order_maker.rb → order.rb} +8 -8
- data/lib/dbee/providers/active_record_provider/expression_builder/select.rb +71 -0
- data/lib/dbee/providers/active_record_provider/expression_builder/where.rb +68 -0
- data/lib/dbee/providers/active_record_provider/version.rb +1 -1
- data/spec/db_helper.rb +130 -14
- data/spec/dbee/providers/active_record_provider_spec.rb +102 -1
- data/spec/fixtures/active_record_snapshots/one_table_query_with_filters.yaml +24 -16
- data/spec/fixtures/active_record_snapshots/two_table_query_with_aggregation.yaml +71 -0
- data/spec/fixtures/active_record_snapshots/two_table_query_with_pivoting.yaml +88 -0
- data/spec/fixtures/models.yaml +20 -0
- metadata +32 -23
- data/lib/dbee/providers/active_record_provider/expression_builder/select_maker.rb +0 -33
- data/lib/dbee/providers/active_record_provider/expression_builder/where_maker.rb +0 -56
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f8de8c388637ba455ffca406ff42751ecfb62f4a40bbcb9f560a085b585568a1
|
4
|
+
data.tar.gz: 9723886e37e886d7df7fa4661df4c81d35fe186d08a7d82de66083e08ed44bac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be75ae14b3c4b0154a0cc4f63950fa86a5f9f16272cd2d5133a4fc4649e2bcd6f7dc5836cf1706c7a9a6b9dba1f1183783038b013bc9e78f5cc5f25e9c980f78
|
7
|
+
data.tar.gz: 7f466202c164f0da7fb936010bc73393a0643682588f14093979fa1e8844f36bd0978020f3f435624d6bea25d95767af35884c6e453a35f41d219dd4331b22d7
|
data/.rubocop.yml
CHANGED
@@ -1,6 +1,20 @@
|
|
1
|
-
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.5
|
3
|
+
|
4
|
+
Layout/LineLength:
|
2
5
|
Max: 100
|
3
6
|
|
7
|
+
Lint/RaiseException:
|
8
|
+
Enabled: True
|
9
|
+
|
10
|
+
Lint/StructNewOverride:
|
11
|
+
Enabled: True
|
12
|
+
|
13
|
+
Metrics/AbcSize:
|
14
|
+
Max: 16
|
15
|
+
Exclude:
|
16
|
+
- spec/db_helper.rb
|
17
|
+
|
4
18
|
Metrics/BlockLength:
|
5
19
|
ExcludedMethods:
|
6
20
|
- let
|
@@ -13,18 +27,19 @@ Metrics/BlockLength:
|
|
13
27
|
- spec/dbee/**/*
|
14
28
|
- dbee-active_record.gemspec
|
15
29
|
|
30
|
+
Metrics/ClassLength:
|
31
|
+
Max: 125
|
32
|
+
|
16
33
|
Metrics/MethodLength:
|
17
34
|
Max: 25
|
18
35
|
Exclude:
|
19
36
|
- spec/db_helper.rb
|
20
37
|
|
21
|
-
|
22
|
-
|
38
|
+
Style/HashEachMethods:
|
39
|
+
Enabled: True
|
23
40
|
|
24
|
-
|
25
|
-
|
26
|
-
Exclude:
|
27
|
-
- spec/db_helper.rb
|
41
|
+
Style/HashTransformKeys:
|
42
|
+
Enabled: True
|
28
43
|
|
29
|
-
|
30
|
-
|
44
|
+
Style/HashTransformValues:
|
45
|
+
Enabled: True
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.6.
|
1
|
+
2.6.6
|
data/.travis.yml
CHANGED
@@ -6,16 +6,12 @@ services:
|
|
6
6
|
- mysql
|
7
7
|
rvm:
|
8
8
|
# Build on the latest stable of all supported Rubies (https://www.ruby-lang.org/en/downloads/):
|
9
|
-
- 2.
|
10
|
-
- 2.
|
11
|
-
- 2.
|
9
|
+
- 2.5.8
|
10
|
+
- 2.6.6
|
11
|
+
- 2.7.1
|
12
12
|
env:
|
13
13
|
- AR_VERSION=5
|
14
14
|
- AR_VERSION=6
|
15
|
-
matrix:
|
16
|
-
exclude:
|
17
|
-
- rvm: 2.4.6
|
18
|
-
env: AR_VERSION=6
|
19
15
|
cache: bundler
|
20
16
|
before_script:
|
21
17
|
- curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,33 @@
|
|
1
|
+
# 2.1.0 (TBD)
|
2
|
+
|
3
|
+
### Additions:
|
4
|
+
|
5
|
+
* Implemented Dbee::Query::Field#aggregator
|
6
|
+
* Implemented Dbee::Query::Field#filters
|
7
|
+
|
8
|
+
### Changes:
|
9
|
+
|
10
|
+
* Bumped minimum Ruby version to 2.5
|
11
|
+
|
12
|
+
# 2.0.4 (February 13th, 2020)
|
13
|
+
|
14
|
+
* use Arel#in for Equal filters when there is more than one value
|
15
|
+
* use Arel#not_in for NotEqual filters when there are is than one value
|
16
|
+
|
17
|
+
# 2.0.3 (January 7th, 2020)
|
18
|
+
|
19
|
+
* Added/tested support for Dbee 2.0.3
|
20
|
+
* Added support for Ruby 2.6.5
|
21
|
+
|
22
|
+
# 2.0.2 (November 7th, 2019)
|
23
|
+
|
24
|
+
* Added/tested support for Dbee 2.0.2
|
25
|
+
* Added support for Ruby 2.3.8
|
26
|
+
|
27
|
+
# 2.0.1 (October 25th, 2019)
|
28
|
+
|
29
|
+
* Development dependency updates.
|
30
|
+
|
1
31
|
# 2.0.0 (September 3rd, 2019)
|
2
32
|
|
3
33
|
* Only support Dbee version 2.0.0 and above
|
data/dbee-active_record.gemspec
CHANGED
@@ -15,11 +15,19 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.email = ['mruggio@bluemarblepayroll.com']
|
16
16
|
s.files = `git ls-files`.split("\n")
|
17
17
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
-
s.
|
18
|
+
s.bindir = 'exe'
|
19
|
+
s.executables = []
|
19
20
|
s.homepage = 'https://github.com/bluemarblepayroll/dbee-active_record'
|
20
21
|
s.license = 'MIT'
|
22
|
+
s.metadata = {
|
23
|
+
'bug_tracker_uri' => 'https://github.com/bluemarblepayroll/dbee-active_record/issues',
|
24
|
+
'changelog_uri' => 'https://github.com/bluemarblepayroll/dbee-active_record/blob/master/CHANGELOG.md',
|
25
|
+
'documentation_uri' => 'https://www.rubydoc.info/gems/dbee-active_record',
|
26
|
+
'homepage_uri' => s.homepage,
|
27
|
+
'source_code_uri' => s.homepage
|
28
|
+
}
|
21
29
|
|
22
|
-
s.required_ruby_version = '>= 2.
|
30
|
+
s.required_ruby_version = '>= 2.5'
|
23
31
|
|
24
32
|
ar_version = ENV['AR_VERSION'] || ''
|
25
33
|
|
@@ -34,15 +42,15 @@ Gem::Specification.new do |s|
|
|
34
42
|
end
|
35
43
|
|
36
44
|
s.add_dependency('activerecord', activerecord_version)
|
37
|
-
s.add_dependency('dbee', '
|
45
|
+
s.add_dependency('dbee', '=2.1.0.pre.alpha')
|
38
46
|
|
39
47
|
s.add_development_dependency('guard-rspec', '~>4.7')
|
40
48
|
s.add_development_dependency('mysql2', '~>0.5')
|
41
49
|
s.add_development_dependency('pry', '~>0')
|
42
|
-
s.add_development_dependency('rake', '~>
|
50
|
+
s.add_development_dependency('rake', '~> 13')
|
43
51
|
s.add_development_dependency('rspec', '~> 3.8')
|
44
|
-
s.add_development_dependency('rubocop', '~>0.
|
52
|
+
s.add_development_dependency('rubocop', '~>0.81.0')
|
45
53
|
s.add_development_dependency('simplecov', '~>0.17.0')
|
46
|
-
s.add_development_dependency('simplecov-console', '~>0.
|
54
|
+
s.add_development_dependency('simplecov-console', '~>0.7.0')
|
47
55
|
s.add_development_dependency('sqlite3', '~>1')
|
48
56
|
end
|
data/exe/.gitkeep
ADDED
File without changes
|
@@ -7,26 +7,24 @@
|
|
7
7
|
# LICENSE file in the root directory of this source tree.
|
8
8
|
#
|
9
9
|
|
10
|
-
require_relative 'expression_builder/
|
11
|
-
require_relative 'expression_builder/
|
12
|
-
require_relative 'expression_builder/
|
13
|
-
require_relative 'expression_builder/
|
10
|
+
require_relative 'expression_builder/constraint'
|
11
|
+
require_relative 'expression_builder/order'
|
12
|
+
require_relative 'expression_builder/select'
|
13
|
+
require_relative 'expression_builder/where'
|
14
14
|
|
15
15
|
module Dbee
|
16
16
|
module Providers
|
17
17
|
class ActiveRecordProvider
|
18
18
|
# This class can generate an Arel expression tree.
|
19
19
|
class ExpressionBuilder
|
20
|
-
extend Forwardable
|
21
|
-
|
22
20
|
class MissingConstraintError < StandardError; end
|
23
21
|
|
24
|
-
def_delegators :statement, :to_sql
|
25
|
-
|
26
22
|
def initialize(model, table_alias_maker, column_alias_maker)
|
27
23
|
@model = model
|
28
24
|
@table_alias_maker = table_alias_maker
|
29
25
|
@column_alias_maker = column_alias_maker
|
26
|
+
@requires_group_by = false
|
27
|
+
@group_by_columns = []
|
30
28
|
|
31
29
|
clear
|
32
30
|
end
|
@@ -49,13 +47,25 @@ module Dbee
|
|
49
47
|
self
|
50
48
|
end
|
51
49
|
|
50
|
+
def to_sql
|
51
|
+
if requires_group_by
|
52
|
+
@requires_group_by = false
|
53
|
+
statement.group(group_by_columns) unless group_by_columns.empty?
|
54
|
+
@group_by_columns = []
|
55
|
+
end
|
56
|
+
|
57
|
+
statement.to_sql
|
58
|
+
end
|
59
|
+
|
52
60
|
private
|
53
61
|
|
54
62
|
attr_reader :base_table,
|
55
63
|
:statement,
|
56
64
|
:model,
|
57
65
|
:table_alias_maker,
|
58
|
-
:column_alias_maker
|
66
|
+
:column_alias_maker,
|
67
|
+
:requires_group_by,
|
68
|
+
:group_by_columns
|
59
69
|
|
60
70
|
def tables
|
61
71
|
@tables ||= {}
|
@@ -70,7 +80,7 @@ module Dbee
|
|
70
80
|
|
71
81
|
key_path = filter.key_path
|
72
82
|
arel_column = key_paths_to_arel_columns[key_path]
|
73
|
-
predicate =
|
83
|
+
predicate = Where.instance.make(filter, arel_column)
|
74
84
|
|
75
85
|
build(statement.where(predicate))
|
76
86
|
|
@@ -82,22 +92,40 @@ module Dbee
|
|
82
92
|
|
83
93
|
key_path = sorter.key_path
|
84
94
|
arel_column = key_paths_to_arel_columns[key_path]
|
85
|
-
predicate =
|
95
|
+
predicate = Order.instance.make(sorter, arel_column)
|
86
96
|
|
87
97
|
build(statement.order(predicate))
|
88
98
|
|
89
99
|
self
|
90
100
|
end
|
91
101
|
|
102
|
+
def add_filter_key_paths(filters)
|
103
|
+
filters.each_with_object({}) do |filter, memo|
|
104
|
+
arel_key_column = add_key_path(filter.key_path)
|
105
|
+
|
106
|
+
memo[arel_key_column] = filter
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
92
110
|
def add_field(field)
|
93
|
-
add_key_path(field.key_path)
|
111
|
+
arel_value_column = add_key_path(field.key_path)
|
112
|
+
arel_key_columns_to_filters = add_filter_key_paths(field.filters)
|
94
113
|
|
95
|
-
|
96
|
-
|
97
|
-
|
114
|
+
predicate = Select.instance.make(
|
115
|
+
field,
|
116
|
+
arel_key_columns_to_filters,
|
117
|
+
arel_value_column,
|
118
|
+
column_alias_maker
|
119
|
+
)
|
98
120
|
|
99
121
|
build(statement.project(predicate))
|
100
122
|
|
123
|
+
if field.aggregator?
|
124
|
+
@requires_group_by = true
|
125
|
+
else
|
126
|
+
group_by_columns << arel_value_column
|
127
|
+
end
|
128
|
+
|
101
129
|
self
|
102
130
|
end
|
103
131
|
|
@@ -123,7 +151,7 @@ module Dbee
|
|
123
151
|
def table(name, model, previous_table)
|
124
152
|
table = make_table(model.table, name)
|
125
153
|
|
126
|
-
on =
|
154
|
+
on = Constraint.instance.make(model.constraints, table, previous_table)
|
127
155
|
|
128
156
|
raise MissingConstraintError, "for: #{name}" unless on
|
129
157
|
|
@@ -142,16 +170,16 @@ module Dbee
|
|
142
170
|
end
|
143
171
|
|
144
172
|
def add_key_path(key_path)
|
145
|
-
return if key_paths_to_arel_columns.key?(key_path)
|
173
|
+
return key_paths_to_arel_columns[key_path] if key_paths_to_arel_columns.key?(key_path)
|
146
174
|
|
147
175
|
ancestors = model.ancestors!(key_path.ancestor_names)
|
148
176
|
|
149
177
|
table = traverse_ancestors(ancestors)
|
150
178
|
|
151
179
|
arel_column = table[key_path.column_name]
|
152
|
-
key_paths_to_arel_columns[key_path] = arel_column
|
153
180
|
|
154
|
-
|
181
|
+
# Note that this returns arel_column
|
182
|
+
key_paths_to_arel_columns[key_path] = arel_column
|
155
183
|
end
|
156
184
|
|
157
185
|
def build(new_expression)
|
@@ -12,9 +12,19 @@ module Dbee
|
|
12
12
|
class ActiveRecordProvider
|
13
13
|
class ExpressionBuilder
|
14
14
|
# Can derive constraints for Arel table JOIN statements.
|
15
|
-
class
|
15
|
+
class Constraint
|
16
16
|
include Singleton
|
17
17
|
|
18
|
+
def make(constraints, table, previous_table)
|
19
|
+
constraints.inject(nil) do |memo, constraint|
|
20
|
+
method = CONSTRAINT_RESOLVERS[constraint.class]
|
21
|
+
|
22
|
+
raise ArgumentError, "constraint unhandled: #{constraint.class.name}" unless method
|
23
|
+
|
24
|
+
method.call(constraint, memo, table, previous_table)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
18
28
|
CONCAT_METHOD = lambda do |on, arel_column, value|
|
19
29
|
on ? on.and(arel_column.eq(value)) : arel_column.eq(value)
|
20
30
|
end
|
@@ -44,16 +54,6 @@ module Dbee
|
|
44
54
|
}.freeze
|
45
55
|
|
46
56
|
private_constant :CONSTRAINT_RESOLVERS
|
47
|
-
|
48
|
-
def make(constraints, table, previous_table)
|
49
|
-
constraints.inject(nil) do |memo, constraint|
|
50
|
-
method = CONSTRAINT_RESOLVERS[constraint.class]
|
51
|
-
|
52
|
-
raise ArgumentError, "constraint unhandled: #{constraint.class.name}" unless method
|
53
|
-
|
54
|
-
method.call(constraint, memo, table, previous_table)
|
55
|
-
end
|
56
|
-
end
|
57
57
|
end
|
58
58
|
end
|
59
59
|
end
|
data/lib/dbee/providers/active_record_provider/expression_builder/{order_maker.rb → order.rb}
RENAMED
@@ -12,16 +12,9 @@ module Dbee
|
|
12
12
|
class ActiveRecordProvider
|
13
13
|
class ExpressionBuilder
|
14
14
|
# Derives Arel#order predicates.
|
15
|
-
class
|
15
|
+
class Order
|
16
16
|
include Singleton
|
17
17
|
|
18
|
-
SORTER_EVALUATORS = {
|
19
|
-
Query::Sorters::Ascending => ->(column) { column },
|
20
|
-
Query::Sorters::Descending => ->(column) { column.desc }
|
21
|
-
}.freeze
|
22
|
-
|
23
|
-
private_constant :SORTER_EVALUATORS
|
24
|
-
|
25
18
|
def make(sorter, arel_column)
|
26
19
|
method = SORTER_EVALUATORS[sorter.class]
|
27
20
|
|
@@ -29,6 +22,13 @@ module Dbee
|
|
29
22
|
|
30
23
|
method.call(arel_column)
|
31
24
|
end
|
25
|
+
|
26
|
+
SORTER_EVALUATORS = {
|
27
|
+
Query::Sorters::Ascending => ->(column) { column },
|
28
|
+
Query::Sorters::Descending => ->(column) { column.desc }
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
private_constant :SORTER_EVALUATORS
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
@@ -0,0 +1,71 @@
|
|
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
|
+
class ActiveRecordProvider
|
13
|
+
class ExpressionBuilder
|
14
|
+
# Derives Arel#project predicates.
|
15
|
+
class Select
|
16
|
+
include Singleton
|
17
|
+
|
18
|
+
def make(field, arel_key_nodes_to_filters, arel_value_node, alias_maker)
|
19
|
+
column_alias = quote(alias_maker.make(field.display))
|
20
|
+
predicate = expression(field, arel_key_nodes_to_filters, arel_value_node)
|
21
|
+
predicate = aggregate(field, predicate)
|
22
|
+
|
23
|
+
predicate.as(column_alias)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
AGGREGRATOR_EVALUATORS = {
|
29
|
+
nil => ->(arel_node) { arel_node },
|
30
|
+
Query::Field::Aggregator::AVE => ->(node) { Arel::Nodes::Avg.new([node]) },
|
31
|
+
Query::Field::Aggregator::COUNT => ->(node) { Arel::Nodes::Count.new([node]) },
|
32
|
+
Query::Field::Aggregator::MAX => ->(node) { Arel::Nodes::Max.new([node]) },
|
33
|
+
Query::Field::Aggregator::MIN => ->(node) { Arel::Nodes::Min.new([node]) },
|
34
|
+
Query::Field::Aggregator::SUM => ->(node) { Arel::Nodes::Sum.new([node]) }
|
35
|
+
}.freeze
|
36
|
+
|
37
|
+
private_constant :AGGREGRATOR_EVALUATORS
|
38
|
+
|
39
|
+
def quote(value)
|
40
|
+
ActiveRecord::Base.connection.quote(value)
|
41
|
+
end
|
42
|
+
|
43
|
+
def aggregate(field, predicate)
|
44
|
+
AGGREGRATOR_EVALUATORS[field.aggregator].call(predicate)
|
45
|
+
end
|
46
|
+
|
47
|
+
def expression(field, arel_key_nodes_to_filters, arel_value_node)
|
48
|
+
if field.filters?
|
49
|
+
case_statement = Arel::Nodes::Case.new
|
50
|
+
filter_predicate = make_filter_predicate(arel_key_nodes_to_filters)
|
51
|
+
|
52
|
+
case_statement.when(filter_predicate).then(arel_value_node)
|
53
|
+
else
|
54
|
+
arel_value_node
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def make_filter_predicate(arel_key_nodes_to_filters)
|
59
|
+
predicates = arel_key_nodes_to_filters.map do |arel_key_node, filter|
|
60
|
+
Where.instance.make(filter, arel_key_node)
|
61
|
+
end
|
62
|
+
|
63
|
+
predicates.inject(predicates.shift) do |memo, predicate|
|
64
|
+
memo.and(predicate)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|