dbee-active_record 2.0.1 → 2.1.0.pre.alpha.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ff03ff4358df26cd80685c990ec5c24d2998988c5766d39bace3961f0902caa
4
- data.tar.gz: 282d6172b8b6ae6cf809711f271938c2dabd0fcf5dd2dd377aab3acd53cd83d3
3
+ metadata.gz: 75d42ea7f3b391ad5014264a4b914339bf7dff8582aebe85c4c3180576ae7648
4
+ data.tar.gz: e63553d0dcc906a38b336767dfcf10bcc162807682ae866f4450a80185993b8e
5
5
  SHA512:
6
- metadata.gz: 920601f06a18f8bf4b8ad450b4ecda4a484059ee7ccc0afee2cff955c10b9098303332ba121cc638a0e9ba925b5067c24ecdd7c8e6beb8a339448182f2a5f0de
7
- data.tar.gz: 25811b6619d35c0373f62f9061a15ed5e3574010969b7b5bbefae9110c9194a870704365248309bc8cc49a5c4770af0d6ede7fc9a9cacc01c550f1f3c709d8e7
6
+ metadata.gz: f727e65305615a6e5b2450654ce4bb174674970be4b6855922035dac6bc8378862a003f0c1ec4677433d560dee4f5ca3b9094b57a4be8eae6a7d78720c805910
7
+ data.tar.gz: 21d91630d8ba8407550dcdd6d8a7aab9bc7dc52ad39679ed5b673f59263f2761e55505c61b8cafcc2ce09a31fee1571e58e9fffc76b24f1dd7a5d3d62e3b4ef7
@@ -1,6 +1,20 @@
1
- Metrics/LineLength:
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
- AllCops:
22
- TargetRubyVersion: 2.4
38
+ Style/HashEachMethods:
39
+ Enabled: True
23
40
 
24
- Metrics/AbcSize:
25
- Max: 16
26
- Exclude:
27
- - spec/db_helper.rb
41
+ Style/HashTransformKeys:
42
+ Enabled: True
28
43
 
29
- Metrics/ClassLength:
30
- Max: 125
44
+ Style/HashTransformValues:
45
+ Enabled: True
@@ -1 +1 @@
1
- 2.6.3
1
+ 2.6.6
@@ -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.4.6
10
- - 2.5.5
11
- - 2.6.3
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
@@ -1,3 +1,29 @@
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
+
1
27
  # 2.0.1 (October 25th, 2019)
2
28
 
3
29
  * Development dependency updates.
@@ -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.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
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.4.6'
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', '~>2')
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
50
  s.add_development_dependency('rake', '~> 13')
43
51
  s.add_development_dependency('rspec', '~> 3.8')
44
- s.add_development_dependency('rubocop', '~>0.75.1')
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.5.0')
54
+ s.add_development_dependency('simplecov-console', '~>0.7.0')
47
55
  s.add_development_dependency('sqlite3', '~>1')
48
56
  end
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/constraint_maker'
11
- require_relative 'expression_builder/order_maker'
12
- require_relative 'expression_builder/select_maker'
13
- require_relative 'expression_builder/where_maker'
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 = WhereMaker.instance.make(filter, arel_column)
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 = OrderMaker.instance.make(sorter, arel_column)
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
- key_path = field.key_path
96
- arel_column = key_paths_to_arel_columns[key_path]
97
- predicate = SelectMaker.instance.make(field, arel_column, column_alias_maker)
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 = ConstraintMaker.instance.make(model.constraints, table, previous_table)
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
- self
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 ConstraintMaker
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
@@ -12,16 +12,9 @@ module Dbee
12
12
  class ActiveRecordProvider
13
13
  class ExpressionBuilder
14
14
  # Derives Arel#order predicates.
15
- class OrderMaker
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
@@ -0,0 +1,91 @@
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#where predicates.
15
+ class Where
16
+ include Singleton
17
+
18
+ def make(filter, arel_column)
19
+ # If the filter has a value of nil, then simply return an IS NULL predicate
20
+ return make_is_null_predicate(arel_column) unless filter.value
21
+
22
+ values = Array(filter.value).flatten
23
+
24
+ # This logic helps ensure that if a null exists that it translates to an IS NULL
25
+ # predicate and does not get put into an in or not_in clause.
26
+ predicates = values.include?(nil) ? [make_is_null_predicate(arel_column)] : []
27
+ predicates += make_predicates(filter, arel_column, values - [nil])
28
+
29
+ # Chain all predicates together
30
+ predicates.inject(predicates.shift) do |memo, predicate|
31
+ memo.or(predicate)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ FILTER_EVALUATORS = {
38
+ Query::Filters::Contains => ->(node, val) { node.matches("%#{val}%") },
39
+ Query::Filters::Equals => ->(node, val) { node.eq(val) },
40
+ Query::Filters::GreaterThan => ->(node, val) { node.gt(val) },
41
+ Query::Filters::GreaterThanOrEqualTo => ->(node, val) { node.gteq(val) },
42
+ Query::Filters::LessThan => ->(node, val) { node.lt(val) },
43
+ Query::Filters::LessThanOrEqualTo => ->(node, val) { node.lteq(val) },
44
+ Query::Filters::NotContain => ->(node, val) { node.does_not_match("%#{val}%") },
45
+ Query::Filters::NotEquals => ->(node, val) { node.not_eq(val) },
46
+ Query::Filters::NotStartWith => ->(node, val) { node.does_not_match("#{val}%") },
47
+ Query::Filters::StartsWith => ->(node, val) { node.matches("#{val}%") }
48
+ }.freeze
49
+
50
+ private_constant :FILTER_EVALUATORS
51
+
52
+ def make_predicates(filter, arel_column, values)
53
+ if use_in?(filter, values)
54
+ [arel_column.in(values)]
55
+ elsif use_not_in?(filter, values)
56
+ [arel_column.not_in(values)]
57
+ else
58
+ make_or_predicates(filter, arel_column, values)
59
+ end
60
+ end
61
+
62
+ def use_in?(filter, values)
63
+ filter.is_a?(Query::Filters::Equals) && values.length > 1
64
+ end
65
+
66
+ def use_not_in?(filter, values)
67
+ filter.is_a?(Query::Filters::NotEquals) && values.length > 1
68
+ end
69
+
70
+ def make_or_predicates(filter, arel_column, values)
71
+ values.map do |value|
72
+ make_predicate(arel_column, filter.class, value)
73
+ end
74
+ end
75
+
76
+ def make_predicate(arel_column, filter_class, value)
77
+ method = FILTER_EVALUATORS[filter_class]
78
+
79
+ raise ArgumentError, "cannot compile filter: #{filter}" unless method
80
+
81
+ method.call(arel_column, value)
82
+ end
83
+
84
+ def make_is_null_predicate(arel_column)
85
+ make_predicate(arel_column, Query::Filters::Equals, nil)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end