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

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