dbee-active_record 2.1.0.pre.alpha.1 → 2.3.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +84 -0
  3. data/.rubocop.yml +4 -18
  4. data/.tool-versions +1 -0
  5. data/CHANGELOG.md +19 -1
  6. data/Guardfile +2 -1
  7. data/README.md +4 -4
  8. data/dbee-active_record.gemspec +11 -13
  9. data/lib/dbee/providers/active_record_provider/expression_builder.rb +81 -59
  10. data/lib/dbee/providers/active_record_provider/maker.rb +37 -0
  11. data/lib/dbee/providers/active_record_provider/{expression_builder → makers}/constraint.rb +1 -1
  12. data/lib/dbee/providers/active_record_provider/{expression_builder → makers}/order.rb +1 -1
  13. data/lib/dbee/providers/active_record_provider/{expression_builder → makers}/select.rb +13 -3
  14. data/lib/dbee/providers/active_record_provider/{expression_builder → makers}/where.rb +27 -7
  15. data/lib/dbee/providers/active_record_provider/version.rb +1 -1
  16. data/lib/dbee/providers/active_record_provider.rb +3 -3
  17. data/spec/db_helper.rb +7 -3
  18. data/spec/dbee/providers/active_record_provider/expression_builder_spec.rb +90 -0
  19. data/spec/dbee/providers/active_record_provider/makers/where_spec.rb +260 -0
  20. data/spec/dbee/providers/active_record_provider_spec.rb +15 -18
  21. data/spec/fixtures/active_record_snapshots/five_table_query.yaml +1 -0
  22. data/spec/fixtures/active_record_snapshots/multiple_same_table_query_with_static_constraints.yaml +1 -0
  23. data/spec/fixtures/active_record_snapshots/one_table_empty_query.yaml +11 -0
  24. data/spec/fixtures/active_record_snapshots/one_table_query.yaml +1 -0
  25. data/spec/fixtures/active_record_snapshots/one_table_query_with_ascending_sort.yaml +1 -0
  26. data/spec/fixtures/active_record_snapshots/one_table_query_with_descending_sort.yaml +1 -0
  27. data/spec/fixtures/active_record_snapshots/one_table_query_with_filters.yaml +1 -0
  28. data/spec/fixtures/active_record_snapshots/one_table_query_with_limit.yaml +1 -0
  29. data/spec/fixtures/active_record_snapshots/one_table_query_with_multiple_sorts.yaml +1 -0
  30. data/spec/fixtures/active_record_snapshots/partitioner_example_1_query.yaml +1 -0
  31. data/spec/fixtures/active_record_snapshots/partitioner_example_2_query.yaml +1 -0
  32. data/spec/fixtures/active_record_snapshots/reverse_polymorphic_query.yaml +1 -0
  33. data/spec/fixtures/active_record_snapshots/two_table_query.yaml +6 -0
  34. data/spec/fixtures/active_record_snapshots/two_table_query_with_aggregation.yaml +1 -0
  35. data/spec/fixtures/active_record_snapshots/two_table_query_with_pivoting.yaml +1 -0
  36. data/spec/fixtures/models.yaml +110 -102
  37. data/spec/spec_helper.rb +13 -2
  38. metadata +108 -27
  39. data/.travis.yml +0 -31
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 75d42ea7f3b391ad5014264a4b914339bf7dff8582aebe85c4c3180576ae7648
4
- data.tar.gz: e63553d0dcc906a38b336767dfcf10bcc162807682ae866f4450a80185993b8e
3
+ metadata.gz: 6b68d2e6c79fa4d3bb1ff26726983750cc112f60af4355eee8795cd748d9be9c
4
+ data.tar.gz: 5e0a15f3f85f465d7bc6fc79a9c74c6847e72be6a5a4ac25ec626f54bec985c4
5
5
  SHA512:
6
- metadata.gz: f727e65305615a6e5b2450654ce4bb174674970be4b6855922035dac6bc8378862a003f0c1ec4677433d560dee4f5ca3b9094b57a4be8eae6a7d78720c805910
7
- data.tar.gz: 21d91630d8ba8407550dcdd6d8a7aab9bc7dc52ad39679ed5b673f59263f2761e55505c61b8cafcc2ce09a31fee1571e58e9fffc76b24f1dd7a5d3d62e3b4ef7
6
+ metadata.gz: 23bb62872a7c2412f0e8745b392b93fbd0c5bc1c7cfaefdc6503f9add73566b774378a69efe82eea53ee9f20c461585d163b96cc715541133b825f4906f87b89
7
+ data.tar.gz: cff34e878e7abb09865d88893af53e4d578077742ed4768fcee6c612c546e44a5a4f340ea2f9f6051a60ce2a62d484fce411c15487a69da00ad04fa5cf8a512e
@@ -0,0 +1,84 @@
1
+ version: 2.1
2
+
3
+ orbs:
4
+ status_to_ms_teams: bluemarblepayroll/status_to_ms_teams_pure_bash@1.0.0
5
+
6
+ jobs:
7
+ build:
8
+ parameters:
9
+ use-bundler-cache:
10
+ type: boolean
11
+ default: true
12
+
13
+ docker:
14
+ - image: circleci/ruby:2.6.6-buster
15
+ environment:
16
+ FORBID_FOCUSED_SPECS: 1
17
+ AR_VERSION: 5
18
+ - image: mysql@sha256:f3515b6a6502d872d5a37db78e4d225c0fcbf8da65d1faf8ce4609c92e2cbaf0
19
+ environment:
20
+ MYSQL_DATABASE: dbee_test
21
+ MYSQL_ALLOW_EMPTY_PASSWORD: 1
22
+ MYSQL_USER: root
23
+ command:
24
+ mysqld --sql_mode=TRADITIONAL,NO_AUTO_VALUE_ON_ZERO
25
+ working_directory: ~/dbee-active_record
26
+ steps:
27
+ - checkout
28
+ - run: sudo apt-get --allow-releaseinfo-change update
29
+ - run: sudo apt-get update && sudo apt-get install -y git curl libmcrypt-dev default-mysql-client
30
+ - run: dockerize -wait tcp://localhost:3306 -timeout 1m
31
+ - run: cp spec/config/database.yaml.ci spec/config/database.yaml
32
+ - run: mysql -h 127.0.0.1 -u root -e 'CREATE DATABASE IF NOT EXISTS dbee_test;'
33
+
34
+ # TODO: wrap bundler caching logic into an Orb:
35
+ - when:
36
+ condition: << parameters.use-bundler-cache >>
37
+ steps:
38
+ - restore_cache:
39
+ key: v1.0.0-build-ruby-dependency-cache-{{ checksum "dbee-active_record.gemspec" }}-{{ checksum "Gemfile" }}-{{ checksum ".ruby-version" }}
40
+
41
+ - run: bundle install --path vendor/bundle
42
+
43
+ - when:
44
+ condition: << parameters.use-bundler-cache >>
45
+ steps:
46
+ - save_cache:
47
+ key: v1.0.0-build-ruby-dependency-cache-{{ checksum "dbee-active_record.gemspec" }}-{{ checksum "Gemfile" }}-{{ checksum ".ruby-version" }}
48
+ paths:
49
+ - vendor/bundle
50
+
51
+ - store_artifacts:
52
+ path: Gemfile.lock
53
+
54
+ - run: bundle exec rubocop
55
+
56
+ - run: COVERAGE=true bundle exec rspec -r rspec_junit_formatter --format progress --format RspecJunitFormatter -o test-results/rspec/results.xml
57
+
58
+ - store_test_results:
59
+ path: test-results
60
+
61
+ - store_artifacts:
62
+ path: coverage
63
+
64
+ - status_to_ms_teams/report:
65
+ webhook_url: $MS_TEAMS_WEBHOOK_URL
66
+
67
+ workflows:
68
+ version: 2.1
69
+ build:
70
+ jobs:
71
+ - build:
72
+ context: org-global
73
+ monthly-gem-dependency-refresh-check:
74
+ triggers:
75
+ - schedule:
76
+ cron: '0 0 1 * *'
77
+ filters:
78
+ branches:
79
+ only:
80
+ - master
81
+ jobs:
82
+ - build:
83
+ context: org-global
84
+ use-bundler-cache: false
data/.rubocop.yml CHANGED
@@ -1,22 +1,17 @@
1
1
  AllCops:
2
2
  TargetRubyVersion: 2.5
3
+ NewCops: enable
3
4
 
4
5
  Layout/LineLength:
5
6
  Max: 100
6
7
 
7
- Lint/RaiseException:
8
- Enabled: True
9
-
10
- Lint/StructNewOverride:
11
- Enabled: True
12
-
13
8
  Metrics/AbcSize:
14
- Max: 16
9
+ Max: 20
15
10
  Exclude:
16
11
  - spec/db_helper.rb
17
12
 
18
13
  Metrics/BlockLength:
19
- ExcludedMethods:
14
+ IgnoredMethods:
20
15
  - let
21
16
  - it
22
17
  - describe
@@ -28,18 +23,9 @@ Metrics/BlockLength:
28
23
  - dbee-active_record.gemspec
29
24
 
30
25
  Metrics/ClassLength:
31
- Max: 125
26
+ Max: 150
32
27
 
33
28
  Metrics/MethodLength:
34
29
  Max: 25
35
30
  Exclude:
36
31
  - spec/db_helper.rb
37
-
38
- Style/HashEachMethods:
39
- Enabled: True
40
-
41
- Style/HashTransformKeys:
42
- Enabled: True
43
-
44
- Style/HashTransformValues:
45
- Enabled: True
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 2.6.6
data/CHANGELOG.md CHANGED
@@ -1,9 +1,27 @@
1
- # 2.1.0 (TBD)
1
+ # 2.3.0 (October 4th, 2021)
2
+
3
+ ### Additions:
4
+
5
+ * Support for Dbee::Query#offset.
6
+ # 2.2.0 (March 11th, 2021)
7
+
8
+ ### Additions:
9
+
10
+ * Support for graph based models.
11
+
12
+ # 2.1.2 (October 15th, 2020)
13
+
14
+ * Improved test coverage for Where maker
15
+ * Fixed bug in Where maker which resulted in only IS NULL predicates being generated, even in cases
16
+ of when IS NOT NULL intended.
17
+
18
+ # 2.1.1 (July 15th, 2020)
2
19
 
3
20
  ### Additions:
4
21
 
5
22
  * Implemented Dbee::Query::Field#aggregator
6
23
  * Implemented Dbee::Query::Field#filters
24
+ * Implemented base case when a Dbee::Query contains no fields
7
25
 
8
26
  ### Changes:
9
27
 
data/Guardfile CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- guard :rspec, cmd: 'DISABLE_SIMPLECOV=true bundle exec rspec --format=documentation' do
3
+ command = 'DISABLE_SIMPLECOV=true bundle exec rspec --format=documentation --order=defined'
4
+ guard :rspec, cmd: command do
4
5
  require 'guard/rspec/dsl'
5
6
  dsl = Guard::RSpec::Dsl.new(self)
6
7
 
data/README.md CHANGED
@@ -1,17 +1,17 @@
1
1
  # Dbee Active Record Provider
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/dbee-active_record.svg)](https://badge.fury.io/rb/dbee-active_record) [![Build Status](https://travis-ci.org/bluemarblepayroll/dbee-active_record.svg?branch=master)](https://travis-ci.org/bluemarblepayroll/dbee-active_record) [![Maintainability](https://api.codeclimate.com/v1/badges/7f74a4e546bebb603cce/maintainability)](https://codeclimate.com/github/bluemarblepayroll/dbee-active_record/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/7f74a4e546bebb603cce/test_coverage)](https://codeclimate.com/github/bluemarblepayroll/dbee-active_record/test_coverage) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
3
+ [![CircleCI](https://circleci.com/bb/bluemarble-ondemand/dbee-active_record/tree/master.svg?style=svg&circle-token=088be69cf3978bc526add53690e4919eb2fcce5c)](https://circleci.com/bb/bluemarble-ondemand/dbee-active_record/tree/master)
4
4
 
5
5
  Dbee does not ship with a SQL generator by default. This library plugs into Dbee to provide SQL generation via ActiveRecord. Technically speaking: this library does not use ActiveRecord for anything except connection information. All actual SQL generation is performed using Arel. There is no actual coupling of your domain ActiveRecord subclasses to Dbee.
6
6
 
7
- This library is a plugin for [Dbee](https://github.com/bluemarblepayroll/dbee). The Dbee repositories README file contains information about how to use the Data Model and Query API's.
7
+ This library is a plugin for Dbee. The Dbee repositories README file contains information about how to use the Data Model and Query API's.
8
8
 
9
9
  ## Installation
10
10
 
11
11
  To install through Rubygems:
12
12
 
13
13
  ````
14
- gem install install dbee-active_record
14
+ gem install dbee-active_record
15
15
  ````
16
16
 
17
17
  You can also add this to your Gemfile:
@@ -76,7 +76,7 @@ Note: ensure you have proper authorization before trying to publish new versions
76
76
  After code changes have successfully gone through the Pull Request review process then the following steps should be followed for publishing new versions:
77
77
 
78
78
  1. Merge Pull Request into master
79
- 2. Update `lib/dbee-active_record/version.rb` using [semantic versioning](https://semver.org/)
79
+ 2. Update `version.rb` using [semantic versioning](https://semver.org/)
80
80
  3. Install dependencies: `bundle`
81
81
  4. Update `CHANGELOG.md` with release notes
82
82
  5. Commit & push master to remote and ensure CI builds master successfully
@@ -11,21 +11,13 @@ Gem::Specification.new do |s|
11
11
  By default Dbee ships with no underlying SQL generator. This library will plug in ActiveRecord into Dbee and Dbee will use it for SQL generation.
12
12
  DESCRIPTION
13
13
 
14
- s.authors = ['Matthew Ruggio']
15
- s.email = ['mruggio@bluemarblepayroll.com']
14
+ s.authors = ['Matthew Ruggio', 'Craig Kattner']
15
+ s.email = ['mruggio@bluemarblepayroll.com', 'ckattner@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
18
  s.bindir = 'exe'
19
19
  s.executables = []
20
- s.homepage = 'https://github.com/bluemarblepayroll/dbee-active_record'
21
20
  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
- }
29
21
 
30
22
  s.required_ruby_version = '>= 2.5'
31
23
 
@@ -42,15 +34,21 @@ Gem::Specification.new do |s|
42
34
  end
43
35
 
44
36
  s.add_dependency('activerecord', activerecord_version)
45
- s.add_dependency('dbee', '=2.1.0.pre.alpha')
37
+ s.add_dependency('dbee', '~>3', '>=3.1.0')
46
38
 
47
39
  s.add_development_dependency('guard-rspec', '~>4.7')
48
40
  s.add_development_dependency('mysql2', '~>0.5')
49
41
  s.add_development_dependency('pry', '~>0')
42
+ s.add_development_dependency('pry-byebug')
50
43
  s.add_development_dependency('rake', '~> 13')
51
44
  s.add_development_dependency('rspec', '~> 3.8')
52
- s.add_development_dependency('rubocop', '~>0.81.0')
53
- s.add_development_dependency('simplecov', '~>0.17.0')
45
+ s.add_development_dependency('rspec_junit_formatter')
46
+ s.add_development_dependency('rubocop', '~> 1')
47
+ s.add_development_dependency('rubocop-rake')
48
+ s.add_development_dependency('rubocop-rspec')
49
+ s.add_development_dependency('simplecov', '~>0.19.0')
54
50
  s.add_development_dependency('simplecov-console', '~>0.7.0')
55
51
  s.add_development_dependency('sqlite3', '~>1')
52
+ # Helpful to spot differences in longer SQL queries:
53
+ s.add_development_dependency('super_diff', '~>0.6')
56
54
  end
@@ -7,52 +7,28 @@
7
7
  # LICENSE file in the root directory of this source tree.
8
8
  #
9
9
 
10
- require_relative 'expression_builder/constraint'
11
- require_relative 'expression_builder/order'
12
- require_relative 'expression_builder/select'
13
- require_relative 'expression_builder/where'
10
+ require_relative 'maker'
14
11
 
15
12
  module Dbee
16
13
  module Providers
17
14
  class ActiveRecordProvider
18
- # This class can generate an Arel expression tree.
19
- class ExpressionBuilder
15
+ # This class can generate an Arel expression tree given a Dbee::Schema
16
+ # and Dbee::Query.
17
+ class ExpressionBuilder < Maker # :nodoc: all
20
18
  class MissingConstraintError < StandardError; end
21
19
 
22
- def initialize(model, table_alias_maker, column_alias_maker)
23
- @model = model
24
- @table_alias_maker = table_alias_maker
25
- @column_alias_maker = column_alias_maker
26
- @requires_group_by = false
27
- @group_by_columns = []
20
+ def initialize(schema, table_alias_maker, column_alias_maker)
21
+ super(column_alias_maker)
28
22
 
29
- clear
23
+ @schema = schema
24
+ @table_alias_maker = table_alias_maker
30
25
  end
31
26
 
32
- def clear
33
- @base_table = make_table(model.table, model.name)
27
+ def to_sql(query)
28
+ reset_query_state
29
+ build_query(query)
34
30
 
35
- build(base_table)
36
-
37
- add_partitioners(base_table, model.partitioners)
38
- end
39
-
40
- def add(query)
41
- query.fields.each { |field| add_field(field) }
42
- query.sorters.each { |sorter| add_sorter(sorter) }
43
- query.filters.each { |filter| add_filter(filter) }
44
-
45
- add_limit(query.limit)
46
-
47
- self
48
- end
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
31
+ return statement.project(select_maker.star(base_table)).to_sql if select_all
56
32
 
57
33
  statement.to_sql
58
34
  end
@@ -60,19 +36,47 @@ module Dbee
60
36
  private
61
37
 
62
38
  attr_reader :base_table,
39
+ :key_paths_to_arel_columns,
40
+ :from_model,
63
41
  :statement,
64
- :model,
65
42
  :table_alias_maker,
66
- :column_alias_maker,
67
43
  :requires_group_by,
68
- :group_by_columns
44
+ :group_by_columns,
45
+ :schema,
46
+ :select_all,
47
+ :tables
48
+
49
+ def reset_query_state
50
+ @base_table = nil
51
+ @key_paths_to_arel_columns = {}
52
+ @from_model = nil
53
+ @group_by_columns = []
54
+ @requires_group_by = false
55
+ @select_all = true
56
+ @tables = {}
57
+ end
58
+
59
+ def build_query(query)
60
+ establish_query_base(query)
61
+ process_fields_sorters_and_filters(query)
62
+
63
+ add_partitioners(base_table, from_model.partitioners)
64
+ add_limit(query.limit)
65
+ add_offset(query.offset)
66
+
67
+ statement.group(group_by_columns) if requires_group_by && !group_by_columns.empty?
68
+ end
69
69
 
70
- def tables
71
- @tables ||= {}
70
+ def establish_query_base(query)
71
+ @from_model = schema.model_for_name!(query.from)
72
+ @base_table = make_table(from_model.table, @from_model.name)
73
+ build(base_table)
72
74
  end
73
75
 
74
- def key_paths_to_arel_columns
75
- @key_paths_to_arel_columns ||= {}
76
+ def process_fields_sorters_and_filters(query)
77
+ query.fields.each { |field| add_field(field) }
78
+ query.sorters.each { |sorter| add_sorter(sorter) }
79
+ query.filters.each { |filter| add_filter(filter) }
76
80
  end
77
81
 
78
82
  def add_filter(filter)
@@ -80,7 +84,7 @@ module Dbee
80
84
 
81
85
  key_path = filter.key_path
82
86
  arel_column = key_paths_to_arel_columns[key_path]
83
- predicate = Where.instance.make(filter, arel_column)
87
+ predicate = where_maker.make(filter, arel_column)
84
88
 
85
89
  build(statement.where(predicate))
86
90
 
@@ -92,7 +96,7 @@ module Dbee
92
96
 
93
97
  key_path = sorter.key_path
94
98
  arel_column = key_paths_to_arel_columns[key_path]
95
- predicate = Order.instance.make(sorter, arel_column)
99
+ predicate = order_maker.make(sorter, arel_column)
96
100
 
97
101
  build(statement.order(predicate))
98
102
 
@@ -108,14 +112,14 @@ module Dbee
108
112
  end
109
113
 
110
114
  def add_field(field)
115
+ @select_all = false
111
116
  arel_value_column = add_key_path(field.key_path)
112
117
  arel_key_columns_to_filters = add_filter_key_paths(field.filters)
113
118
 
114
- predicate = Select.instance.make(
119
+ predicate = select_maker.make(
115
120
  field,
116
121
  arel_key_columns_to_filters,
117
- arel_value_column,
118
- column_alias_maker
122
+ arel_value_column
119
123
  )
120
124
 
121
125
  build(statement.project(predicate))
@@ -137,6 +141,14 @@ module Dbee
137
141
  self
138
142
  end
139
143
 
144
+ def add_offset(offset)
145
+ offset = offset ? offset.to_i : nil
146
+
147
+ build(statement.skip(offset))
148
+
149
+ self
150
+ end
151
+
140
152
  def add_partitioners(table, partitioners)
141
153
  partitioners.each do |partitioner|
142
154
  arel_column = table[partitioner.name]
@@ -148,33 +160,43 @@ module Dbee
148
160
  self
149
161
  end
150
162
 
151
- def table(name, model, previous_table)
152
- table = make_table(model.table, name)
163
+ def table(ancestor_names, relationship, model, previous_table)
164
+ table = make_table(model.table, ancestor_names)
153
165
 
154
- on = Constraint.instance.make(model.constraints, table, previous_table)
166
+ on = constraint_maker.make(relationship.constraints, table, previous_table)
155
167
 
156
- raise MissingConstraintError, "for: #{name}" unless on
168
+ raise MissingConstraintError, "for: #{ancestor_names}" unless on
157
169
 
158
170
  build(statement.join(table, ::Arel::Nodes::OuterJoin))
159
171
  build(statement.on(on))
160
172
 
161
173
  add_partitioners(table, model.partitioners)
162
174
 
163
- tables[name] = table
175
+ tables[ancestor_names] = table
164
176
  end
165
177
 
166
- def traverse_ancestors(ancestors)
167
- ancestors.each_pair.inject(base_table) do |memo, (name, model)|
168
- tables.key?(name) ? tables[name] : table(name, model, memo)
178
+ # Travel the query path returning the table at the end of the path.
179
+ #
180
+ # Side effect: intermediate tables are created along the way and are
181
+ # added to the "tables" hash keyed by path.
182
+ def traverse_query_path(expanded_query_path)
183
+ visited_path = []
184
+
185
+ expanded_query_path.inject(base_table) do |prev_model, (relationship, next_model)|
186
+ visited_path += [relationship.name]
187
+ if tables.key?(visited_path)
188
+ tables[visited_path]
189
+ else
190
+ table(visited_path, relationship, next_model, prev_model)
191
+ end
169
192
  end
170
193
  end
171
194
 
172
195
  def add_key_path(key_path)
173
196
  return key_paths_to_arel_columns[key_path] if key_paths_to_arel_columns.key?(key_path)
174
197
 
175
- ancestors = model.ancestors!(key_path.ancestor_names)
176
-
177
- table = traverse_ancestors(ancestors)
198
+ expanded_query_path = schema.expand_query_path(from_model, key_path)
199
+ table = traverse_query_path(expanded_query_path)
178
200
 
179
201
  arel_column = table[key_path.column_name]
180
202
 
@@ -0,0 +1,37 @@
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 'makers/constraint'
11
+ require_relative 'makers/order'
12
+ require_relative 'makers/select'
13
+ require_relative 'makers/where'
14
+
15
+ module Dbee
16
+ module Providers
17
+ class ActiveRecordProvider
18
+ # This class composes all the maker instances into one for use together.
19
+ class Maker # :nodoc: all
20
+ def initialize(column_alias_maker)
21
+ @column_alias_maker = column_alias_maker
22
+ @constraint_maker = Makers::Constraint.instance
23
+ @order_maker = Makers::Order.instance
24
+ @select_maker = Makers::Select.new(column_alias_maker)
25
+ @where_maker = Makers::Where.instance
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :constraint_maker,
31
+ :order_maker,
32
+ :select_maker,
33
+ :where_maker
34
+ end
35
+ end
36
+ end
37
+ end
@@ -10,7 +10,7 @@
10
10
  module Dbee
11
11
  module Providers
12
12
  class ActiveRecordProvider
13
- class ExpressionBuilder
13
+ module Makers # :nodoc: all
14
14
  # Can derive constraints for Arel table JOIN statements.
15
15
  class Constraint
16
16
  include Singleton
@@ -10,7 +10,7 @@
10
10
  module Dbee
11
11
  module Providers
12
12
  class ActiveRecordProvider
13
- class ExpressionBuilder
13
+ module Makers # :nodoc: all
14
14
  # Derives Arel#order predicates.
15
15
  class Order
16
16
  include Singleton
@@ -10,12 +10,22 @@
10
10
  module Dbee
11
11
  module Providers
12
12
  class ActiveRecordProvider
13
- class ExpressionBuilder
13
+ module Makers # :nodoc: all
14
14
  # Derives Arel#project predicates.
15
15
  class Select
16
- include Singleton
16
+ attr_reader :alias_maker
17
17
 
18
- def make(field, arel_key_nodes_to_filters, arel_value_node, alias_maker)
18
+ def initialize(alias_maker)
19
+ @alias_maker = alias_maker
20
+
21
+ freeze
22
+ end
23
+
24
+ def star(arel_table)
25
+ arel_table[Arel.star]
26
+ end
27
+
28
+ def make(field, arel_key_nodes_to_filters, arel_value_node)
19
29
  column_alias = quote(alias_maker.make(field.display))
20
30
  predicate = expression(field, arel_key_nodes_to_filters, arel_value_node)
21
31
  predicate = aggregate(field, predicate)
@@ -10,20 +10,26 @@
10
10
  module Dbee
11
11
  module Providers
12
12
  class ActiveRecordProvider
13
- class ExpressionBuilder
13
+ module Makers # :nodoc: all
14
14
  # Derives Arel#where predicates.
15
15
  class Where
16
16
  include Singleton
17
17
 
18
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
19
+ # If the filter has a value of nil, then simply return an IS (NOT) NULL predicate
20
+ return make_is_null_predicate(arel_column, filter.class) if filter.value.nil?
21
21
 
22
22
  values = Array(filter.value).flatten
23
23
 
24
24
  # This logic helps ensure that if a null exists that it translates to an IS NULL
25
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)] : []
26
+ predicates =
27
+ if values.include?(nil)
28
+ [make_is_null_predicate(arel_column, filter.class)]
29
+ else
30
+ []
31
+ end
32
+
27
33
  predicates += make_predicates(filter, arel_column, values - [nil])
28
34
 
29
35
  # Chain all predicates together
@@ -47,7 +53,20 @@ module Dbee
47
53
  Query::Filters::StartsWith => ->(node, val) { node.matches("#{val}%") }
48
54
  }.freeze
49
55
 
50
- private_constant :FILTER_EVALUATORS
56
+ NULL_PREDICATE_MAP = {
57
+ Query::Filters::Contains => Query::Filters::Equals,
58
+ Query::Filters::Equals => Query::Filters::Equals,
59
+ Query::Filters::GreaterThan => Query::Filters::Equals,
60
+ Query::Filters::GreaterThanOrEqualTo => Query::Filters::Equals,
61
+ Query::Filters::LessThan => Query::Filters::Equals,
62
+ Query::Filters::LessThanOrEqualTo => Query::Filters::Equals,
63
+ Query::Filters::NotContain => Query::Filters::NotEquals,
64
+ Query::Filters::NotEquals => Query::Filters::NotEquals,
65
+ Query::Filters::NotStartWith => Query::Filters::NotEquals,
66
+ Query::Filters::StartsWith => Query::Filters::Equals
67
+ }.freeze
68
+
69
+ private_constant :FILTER_EVALUATORS, :NULL_PREDICATE_MAP
51
70
 
52
71
  def make_predicates(filter, arel_column, values)
53
72
  if use_in?(filter, values)
@@ -81,8 +100,9 @@ module Dbee
81
100
  method.call(arel_column, value)
82
101
  end
83
102
 
84
- def make_is_null_predicate(arel_column)
85
- make_predicate(arel_column, Query::Filters::Equals, nil)
103
+ def make_is_null_predicate(arel_column, requested_filter_class)
104
+ actual_filter_class = NULL_PREDICATE_MAP[requested_filter_class]
105
+ make_predicate(arel_column, actual_filter_class, nil)
86
106
  end
87
107
  end
88
108
  end
@@ -10,7 +10,7 @@
10
10
  module Dbee
11
11
  module Providers
12
12
  class ActiveRecordProvider
13
- VERSION = '2.1.0-alpha.1'
13
+ VERSION = '2.3.0'
14
14
  end
15
15
  end
16
16
  end
@@ -35,12 +35,12 @@ module Dbee
35
35
  @column_alias_maker = alias_maker(column_prefix)
36
36
  end
37
37
 
38
- def sql(model, query)
38
+ def sql(schema, query)
39
39
  ExpressionBuilder.new(
40
- model,
40
+ schema,
41
41
  table_alias_maker,
42
42
  column_alias_maker
43
- ).add(query).to_sql
43
+ ).to_sql(query)
44
44
  end
45
45
 
46
46
  private