query_helper 0.2.20 → 0.2.28
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +24 -0
- data/.gitignore +1 -0
- data/CODE_OF_CONDUCT.md +1 -1
- data/Gemfile.lock +60 -61
- data/README.md +33 -2
- data/lib/query_helper/filter.rb +4 -2
- data/lib/query_helper/query_helper_concern.rb +2 -2
- data/lib/query_helper/sql_filter.rb +15 -4
- data/lib/query_helper/sql_manipulator.rb +47 -3
- data/lib/query_helper/sql_parser.rb +10 -1
- data/lib/query_helper/sql_sort.rb +46 -28
- data/lib/query_helper/version.rb +1 -1
- data/lib/query_helper.rb +4 -0
- data/query_helper.gemspec +3 -2
- metadata +23 -23
- data/.travis.yml +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c635a27696eb10b152787a3b66132760bf79e3b2e695037bf6dcc2b06b39e9b9
|
4
|
+
data.tar.gz: bfc80f15f647d447a6115423308368c39a2f034938731eb94f91d23a7f4c1f53
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d61105466f4cadc1b111ed38afd7e764c3d3fde76fb4db786029654516fb893df43ccf02e2b6f07693dd118ceabbc15ffa9281b2c1c7dbc388091a4f65b9e300
|
7
|
+
data.tar.gz: b3c6b12c6594e1a3f068ce0d1de484f3e49bc53e1da5c99c6b7c68b27fc9c1f90e334e653b711281f0d93334a2ee95dc666d1d620a17de67ca378117e3e4c806
|
@@ -0,0 +1,24 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on: [push]
|
4
|
+
|
5
|
+
env:
|
6
|
+
RAILS_ENV: test
|
7
|
+
GEMFILE_RUBY_VERSION: 2.7.2
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
test:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
steps:
|
13
|
+
- uses: actions/checkout@v2
|
14
|
+
|
15
|
+
- name: Set up Ruby 2.7.2
|
16
|
+
uses: ruby/setup-ruby@v1
|
17
|
+
with:
|
18
|
+
# Not needed with a .ruby-version file
|
19
|
+
ruby-version: 2.7.2
|
20
|
+
# runs 'bundle install' and caches installed gems automatically
|
21
|
+
bundler-cache: true
|
22
|
+
|
23
|
+
- name: Run tests
|
24
|
+
run: bundle exec rspec spec
|
data/.gitignore
CHANGED
data/CODE_OF_CONDUCT.md
CHANGED
@@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
|
|
55
55
|
## Enforcement
|
56
56
|
|
57
57
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
-
reported by contacting the project team at
|
58
|
+
reported by contacting the project team at opensource@pattern.com. All
|
59
59
|
complaints will be reviewed and investigated and will result in a response that
|
60
60
|
is deemed necessary and appropriate to the circumstances. The project team is
|
61
61
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
data/Gemfile.lock
CHANGED
@@ -1,99 +1,99 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
query_helper (0.2.
|
4
|
+
query_helper (0.2.28)
|
5
5
|
activerecord (> 5)
|
6
6
|
activesupport (> 5)
|
7
|
+
sqlite3
|
7
8
|
|
8
9
|
GEM
|
9
10
|
remote: https://rubygems.org/
|
10
11
|
specs:
|
11
|
-
actionpack (6.
|
12
|
-
actionview (= 6.
|
13
|
-
activesupport (= 6.
|
14
|
-
rack (~> 2.0, >= 2.0.
|
12
|
+
actionpack (6.1.4.1)
|
13
|
+
actionview (= 6.1.4.1)
|
14
|
+
activesupport (= 6.1.4.1)
|
15
|
+
rack (~> 2.0, >= 2.0.9)
|
15
16
|
rack-test (>= 0.6.3)
|
16
17
|
rails-dom-testing (~> 2.0)
|
17
18
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
18
|
-
actionview (6.
|
19
|
-
activesupport (= 6.
|
19
|
+
actionview (6.1.4.1)
|
20
|
+
activesupport (= 6.1.4.1)
|
20
21
|
builder (~> 3.1)
|
21
22
|
erubi (~> 1.4)
|
22
23
|
rails-dom-testing (~> 2.0)
|
23
24
|
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
24
|
-
activemodel (6.
|
25
|
-
activesupport (= 6.
|
26
|
-
activerecord (6.
|
27
|
-
activemodel (= 6.
|
28
|
-
activesupport (= 6.
|
29
|
-
activesupport (6.
|
25
|
+
activemodel (6.1.4.1)
|
26
|
+
activesupport (= 6.1.4.1)
|
27
|
+
activerecord (6.1.4.1)
|
28
|
+
activemodel (= 6.1.4.1)
|
29
|
+
activesupport (= 6.1.4.1)
|
30
|
+
activesupport (6.1.4.1)
|
30
31
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
31
|
-
i18n (>=
|
32
|
-
minitest (
|
33
|
-
tzinfo (~>
|
34
|
-
zeitwerk (~> 2.
|
32
|
+
i18n (>= 1.6, < 2)
|
33
|
+
minitest (>= 5.1)
|
34
|
+
tzinfo (~> 2.0)
|
35
|
+
zeitwerk (~> 2.3)
|
35
36
|
builder (3.2.4)
|
36
37
|
byebug (11.1.3)
|
37
|
-
concurrent-ruby (1.1.
|
38
|
+
concurrent-ruby (1.1.9)
|
38
39
|
crass (1.0.6)
|
39
40
|
diff-lcs (1.4.4)
|
40
|
-
erubi (1.
|
41
|
+
erubi (1.10.0)
|
41
42
|
faker (1.9.6)
|
42
43
|
i18n (>= 0.7)
|
43
|
-
i18n (1.8.
|
44
|
+
i18n (1.8.10)
|
44
45
|
concurrent-ruby (~> 1.0)
|
45
|
-
loofah (2.
|
46
|
+
loofah (2.12.0)
|
46
47
|
crass (~> 1.0.2)
|
47
48
|
nokogiri (>= 1.5.9)
|
48
49
|
method_source (1.0.0)
|
49
|
-
mini_portile2 (2.
|
50
|
-
minitest (5.14.
|
51
|
-
nokogiri (1.
|
52
|
-
mini_portile2 (~> 2.
|
50
|
+
mini_portile2 (2.8.0)
|
51
|
+
minitest (5.14.4)
|
52
|
+
nokogiri (1.13.3)
|
53
|
+
mini_portile2 (~> 2.8.0)
|
53
54
|
racc (~> 1.4)
|
54
|
-
racc (1.
|
55
|
+
racc (1.6.0)
|
55
56
|
rack (2.2.3)
|
56
57
|
rack-test (1.1.0)
|
57
58
|
rack (>= 1.0, < 3)
|
58
59
|
rails-dom-testing (2.0.3)
|
59
60
|
activesupport (>= 4.2.0)
|
60
61
|
nokogiri (>= 1.6)
|
61
|
-
rails-html-sanitizer (1.
|
62
|
+
rails-html-sanitizer (1.4.2)
|
62
63
|
loofah (~> 2.3)
|
63
|
-
railties (6.
|
64
|
-
actionpack (= 6.
|
65
|
-
activesupport (= 6.
|
64
|
+
railties (6.1.4.1)
|
65
|
+
actionpack (= 6.1.4.1)
|
66
|
+
activesupport (= 6.1.4.1)
|
66
67
|
method_source
|
67
|
-
rake (>= 0.
|
68
|
-
thor (
|
69
|
-
rake (13.0.
|
70
|
-
rspec (3.
|
71
|
-
rspec-core (~> 3.
|
72
|
-
rspec-expectations (~> 3.
|
73
|
-
rspec-mocks (~> 3.
|
74
|
-
rspec-core (3.
|
75
|
-
rspec-support (~> 3.
|
76
|
-
rspec-expectations (3.
|
68
|
+
rake (>= 0.13)
|
69
|
+
thor (~> 1.0)
|
70
|
+
rake (13.0.6)
|
71
|
+
rspec (3.10.0)
|
72
|
+
rspec-core (~> 3.10.0)
|
73
|
+
rspec-expectations (~> 3.10.0)
|
74
|
+
rspec-mocks (~> 3.10.0)
|
75
|
+
rspec-core (3.10.1)
|
76
|
+
rspec-support (~> 3.10.0)
|
77
|
+
rspec-expectations (3.10.1)
|
77
78
|
diff-lcs (>= 1.2.0, < 2.0)
|
78
|
-
rspec-support (~> 3.
|
79
|
-
rspec-mocks (3.
|
79
|
+
rspec-support (~> 3.10.0)
|
80
|
+
rspec-mocks (3.10.2)
|
80
81
|
diff-lcs (>= 1.2.0, < 2.0)
|
81
|
-
rspec-support (~> 3.
|
82
|
-
rspec-rails (
|
83
|
-
actionpack (>=
|
84
|
-
activesupport (>=
|
85
|
-
railties (>=
|
86
|
-
rspec-core (~> 3.
|
87
|
-
rspec-expectations (~> 3.
|
88
|
-
rspec-mocks (~> 3.
|
89
|
-
rspec-support (~> 3.
|
90
|
-
rspec-support (3.
|
91
|
-
sqlite3 (1.
|
92
|
-
thor (1.0
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
zeitwerk (2.4.0)
|
82
|
+
rspec-support (~> 3.10.0)
|
83
|
+
rspec-rails (5.0.2)
|
84
|
+
actionpack (>= 5.2)
|
85
|
+
activesupport (>= 5.2)
|
86
|
+
railties (>= 5.2)
|
87
|
+
rspec-core (~> 3.10)
|
88
|
+
rspec-expectations (~> 3.10)
|
89
|
+
rspec-mocks (~> 3.10)
|
90
|
+
rspec-support (~> 3.10)
|
91
|
+
rspec-support (3.10.2)
|
92
|
+
sqlite3 (1.4.2)
|
93
|
+
thor (1.1.0)
|
94
|
+
tzinfo (2.0.4)
|
95
|
+
concurrent-ruby (~> 1.0)
|
96
|
+
zeitwerk (2.4.2)
|
97
97
|
|
98
98
|
PLATFORMS
|
99
99
|
ruby
|
@@ -101,14 +101,13 @@ PLATFORMS
|
|
101
101
|
DEPENDENCIES
|
102
102
|
actionpack
|
103
103
|
activesupport
|
104
|
-
bundler (~> 2.2.
|
104
|
+
bundler (~> 2.2.27)
|
105
105
|
byebug
|
106
106
|
faker (~> 1.9.3)
|
107
107
|
query_helper!
|
108
108
|
rake (~> 13.0)
|
109
109
|
rspec (~> 3.0)
|
110
110
|
rspec-rails
|
111
|
-
sqlite3 (~> 1.3.6)
|
112
111
|
|
113
112
|
BUNDLED WITH
|
114
|
-
2.2.
|
113
|
+
2.2.29
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# QueryHelper
|
2
|
-
[![TravisCI](https://travis-ci.org/iserve-products/query_helper.svg?branch=master)](https://travis-ci.org/iserve-products/query_helper)
|
3
2
|
[![Gem Version](https://badge.fury.io/rb/query_helper.svg)](https://badge.fury.io/rb/query_helper)
|
3
|
+
[![CI](https://github.com/patterninc/query_helper/actions/workflows/ci.yml/badge.svg)](https://github.com/patterninc/query_helper/actions)
|
4
4
|
|
5
5
|
QueryHelper is a ruby gem used to paginate, sort, and filter your API calls in Ruby on Rails using URL params in your HTTP requests. It currently only supports Postgres.
|
6
6
|
|
@@ -88,6 +88,37 @@ Multiple Sorts: `http://www.example.com/resources?sort=resource_name:desc,resour
|
|
88
88
|
|
89
89
|
Lowercase Sort: `http://www.example.com/resources?sort=resource_name:desc:lowercase`
|
90
90
|
|
91
|
+
Custom Sort: `http://www.example.com/resources?custom_sort=resource_name:desc`
|
92
|
+
Example:
|
93
|
+
Custom Sort is basically used for enum based column.
|
94
|
+
```
|
95
|
+
class Customer < ApplicationRecord
|
96
|
+
enum customer_type: {
|
97
|
+
enum1: 0,
|
98
|
+
enum2: 1,
|
99
|
+
enum3: 3
|
100
|
+
}
|
101
|
+
end
|
102
|
+
```
|
103
|
+
|
104
|
+
Usage at Controller
|
105
|
+
|
106
|
+
```
|
107
|
+
class SomeController
|
108
|
+
|
109
|
+
def index
|
110
|
+
sort_column, sort_direction = params[:custom_sort]&.split(':')
|
111
|
+
|
112
|
+
column_sort_order = {
|
113
|
+
column_name: sort_column,
|
114
|
+
direction: sort_direction,
|
115
|
+
sort_values: Customer.send(sort_column.pluralize).values
|
116
|
+
}
|
117
|
+
|
118
|
+
@query_helper.update(query: query, column_sort_order: column_sort_order)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
```
|
91
122
|
#### Filtering
|
92
123
|
|
93
124
|
`filter[column][operator_code]=value`
|
@@ -316,7 +347,7 @@ or
|
|
316
347
|
|
317
348
|
## Contributing
|
318
349
|
|
319
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
350
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/patterninc/query_helper. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
320
351
|
|
321
352
|
## License
|
322
353
|
|
data/lib/query_helper/filter.rb
CHANGED
@@ -3,18 +3,20 @@ require "query_helper/invalid_query_error"
|
|
3
3
|
class QueryHelper
|
4
4
|
class Filter
|
5
5
|
|
6
|
-
attr_accessor :operator, :criterion, :comparate, :operator_code, :bind_variable, :aggregate
|
6
|
+
attr_accessor :operator, :criterion, :comparate, :operator_code, :bind_variable, :aggregate, :qualify_clause
|
7
7
|
|
8
8
|
def initialize(
|
9
9
|
operator_code:,
|
10
10
|
criterion:,
|
11
11
|
comparate:,
|
12
|
-
aggregate: false
|
12
|
+
aggregate: false,
|
13
|
+
qualify_clause: false
|
13
14
|
)
|
14
15
|
@operator_code = operator_code
|
15
16
|
@criterion = criterion # Converts to a string to be inserted into sql.
|
16
17
|
@comparate = comparate
|
17
18
|
@aggregate = aggregate
|
19
|
+
@qualify_clause = qualify_clause
|
18
20
|
@bind_variable = ('a'..'z').to_a.shuffle[0,20].join.to_sym
|
19
21
|
|
20
22
|
translate_operator_code()
|
@@ -31,7 +31,7 @@ class QueryHelper
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def create_query_helper_sort
|
34
|
-
QueryHelper::SqlSort.new(sort_string: params[:sort], sort_tiebreak: params[:sort_tiebreak])
|
34
|
+
QueryHelper::SqlSort.new(sort_string: params[:sort], sort_tiebreak: params[:sort_tiebreak], column_sort_order: params[:column_sort_order])
|
35
35
|
end
|
36
36
|
|
37
37
|
def create_query_helper_associations
|
@@ -48,7 +48,7 @@ class QueryHelper
|
|
48
48
|
def query_helper_params_no_pagination
|
49
49
|
helpers = {}
|
50
50
|
helpers[:sql_filter] = create_query_helper_filter() if params[:filter]
|
51
|
-
helpers[:sql_sort] = create_query_helper_sort() if params[:sort] || params[:sort_tiebreak]
|
51
|
+
helpers[:sql_sort] = create_query_helper_sort() if params[:sort] || params[:sort_tiebreak] || params[:custom_sort]
|
52
52
|
helpers[:associations] = create_query_helper_associations() if params[:include]
|
53
53
|
helpers[:search_string] = params[:search_for] if params[:search_for]
|
54
54
|
helpers
|
@@ -3,11 +3,13 @@ require "query_helper/invalid_query_error"
|
|
3
3
|
class QueryHelper
|
4
4
|
class SqlFilter
|
5
5
|
|
6
|
-
attr_accessor :filter_values, :column_maps
|
6
|
+
attr_accessor :filter_values, :column_maps, :options, :qualify_filters
|
7
7
|
|
8
|
-
def initialize(filter_values: [], column_maps: [])
|
8
|
+
def initialize(filter_values: [], column_maps: [], qualify_filters: [], options: {})
|
9
9
|
@column_maps = column_maps
|
10
10
|
@filter_values = filter_values
|
11
|
+
@options = options
|
12
|
+
@qualify_filters = qualify_filters
|
11
13
|
end
|
12
14
|
|
13
15
|
def create_filters
|
@@ -23,13 +25,22 @@ class QueryHelper
|
|
23
25
|
operator_code: criteria.keys.first,
|
24
26
|
criterion: criteria.values.first,
|
25
27
|
comparate: map.sql_expression,
|
26
|
-
aggregate: map.aggregate
|
28
|
+
aggregate: map.aggregate,
|
29
|
+
qualify_clause: aggregated_attribute?(comparate: map.sql_expression)
|
27
30
|
)
|
28
31
|
end
|
29
32
|
end
|
30
33
|
|
34
|
+
def aggregated_attribute?(comparate:)
|
35
|
+
@options['qualify_clause'] && qualify_filters.include?(comparate)
|
36
|
+
end
|
37
|
+
|
38
|
+
def qualify_clauses
|
39
|
+
@filters.select{ |f| aggregated_attribute?(comparate: f.comparate) }.map(&:sql_string)
|
40
|
+
end
|
41
|
+
|
31
42
|
def where_clauses
|
32
|
-
@filters.select{ |f| f.aggregate == false }.map(&:sql_string)
|
43
|
+
@filters.select{ |f| f.aggregate == false && !f.qualify_clause }.map(&:sql_string)
|
33
44
|
end
|
34
45
|
|
35
46
|
def having_clauses
|
@@ -9,14 +9,17 @@ class QueryHelper
|
|
9
9
|
sql:,
|
10
10
|
where_clauses: nil,
|
11
11
|
having_clauses: nil,
|
12
|
+
qualify_clauses: nil,
|
12
13
|
order_by_clauses: nil,
|
13
14
|
include_limit_clause: false,
|
14
15
|
additional_select_clauses: []
|
15
16
|
)
|
17
|
+
sql = remove_qualified_count(sql)
|
16
18
|
@parser = SqlParser.new(sql)
|
17
19
|
@sql = @parser.sql.dup
|
18
20
|
@where_clauses = where_clauses
|
19
21
|
@having_clauses = having_clauses
|
22
|
+
@qualify_clauses = qualify_clauses
|
20
23
|
@order_by_clauses = order_by_clauses
|
21
24
|
@include_limit_clause = include_limit_clause
|
22
25
|
@additional_select_clauses = additional_select_clauses
|
@@ -24,28 +27,69 @@ class QueryHelper
|
|
24
27
|
|
25
28
|
def build
|
26
29
|
insert_having_clauses()
|
30
|
+
insert_qualify_clauses()
|
27
31
|
insert_where_clauses()
|
28
32
|
insert_select_clauses()
|
29
33
|
insert_order_by_and_limit_clause()
|
34
|
+
insert_qualified_count_clauses if (qualify_clause_applicable? || @qualify_present) && @include_limit_clause
|
35
|
+
|
30
36
|
@sql.squish
|
31
37
|
end
|
32
38
|
|
33
39
|
private
|
34
40
|
|
35
41
|
def insert_select_clauses
|
36
|
-
|
37
|
-
@additional_select_clauses << total_count_clause if @include_limit_clause
|
42
|
+
@additional_select_clauses << count_sql if @include_limit_clause && !(qualify_clause_applicable? || @qualify_present)
|
38
43
|
@sql.insert(@parser.insert_select_index, " , #{@additional_select_clauses.join(", ")} ") if @additional_select_clauses.length > 0
|
39
44
|
end
|
40
45
|
|
46
|
+
def count_sql
|
47
|
+
"count(*) over () as _query_full_count"
|
48
|
+
end
|
49
|
+
|
50
|
+
def qualify_clauses(index)
|
51
|
+
if index == 0
|
52
|
+
"qualified_results AS ( "
|
53
|
+
else
|
54
|
+
", qualified_results AS ( "
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# If modifying something please check remove_qualified_count method too
|
59
|
+
def insert_qualified_count_clauses
|
60
|
+
@sql.insert(@parser.select_index, qualify_clauses(@parser.select_index))
|
61
|
+
@sql.insert(@sql.length, ") SELECT qualified_results.*, #{count_sql} FROM qualified_results")
|
62
|
+
end
|
63
|
+
|
64
|
+
# If modifying something please check insert_qualified_count_clauses method too
|
65
|
+
def remove_qualified_count(unparsed_sql)
|
66
|
+
@qualify_present = unparsed_sql.include?("qualified_results") || unparsed_sql.include?(" qualify ")
|
67
|
+
return unparsed_sql unless @qualify_present
|
68
|
+
|
69
|
+
unparsed_sql = unparsed_sql.gsub(/[\,\s]?qualified_results AS \( /, '')
|
70
|
+
unparsed_sql = unparsed_sql.gsub(") SELECT qualified_results.*, count(*) over () as _query_full_count FROM qualified_results", '')
|
71
|
+
unparsed_sql
|
72
|
+
end
|
73
|
+
|
41
74
|
def insert_where_clauses
|
42
75
|
return unless @where_clauses.length > 0
|
43
76
|
begin_string = @parser.where_included? ? "and" : "where"
|
44
77
|
filter_string = @where_clauses.join(" and ")
|
45
|
-
" #{begin_string} #{filter_string} "
|
46
78
|
@sql.insert(@parser.insert_where_index, " #{begin_string} #{filter_string} ")
|
47
79
|
end
|
48
80
|
|
81
|
+
def insert_qualify_clauses
|
82
|
+
return unless qualify_clause_applicable?
|
83
|
+
|
84
|
+
begin_string = @parser.qualify_included? ? "and" : "qualify"
|
85
|
+
filter_string = @qualify_clauses.join(" and ")
|
86
|
+
@sql.insert(@parser.insert_where_index, " #{begin_string} #{filter_string} ")
|
87
|
+
end
|
88
|
+
|
89
|
+
def qualify_clause_applicable?
|
90
|
+
@qualify_clauses.length > 0
|
91
|
+
end
|
92
|
+
|
49
93
|
def insert_having_clauses
|
50
94
|
return unless @having_clauses.length > 0
|
51
95
|
begin_string = @parser.having_included? ? "and" : "having"
|
@@ -48,6 +48,11 @@ class QueryHelper
|
|
48
48
|
find_index(regex, position)
|
49
49
|
end
|
50
50
|
|
51
|
+
def qualify_index(position=:start)
|
52
|
+
regex = / [Qq][Uu][Aa][Ll][Ii][Ff][Yy] /
|
53
|
+
find_index(regex, position)
|
54
|
+
end
|
55
|
+
|
51
56
|
def group_by_index(position=:start)
|
52
57
|
regex = / [Gg][Rr][Oo][Uu][Pp] [Bb][Yy] /
|
53
58
|
find_index(regex, position)
|
@@ -80,6 +85,10 @@ class QueryHelper
|
|
80
85
|
!where_index.nil?
|
81
86
|
end
|
82
87
|
|
88
|
+
def qualify_included?
|
89
|
+
!qualify_index.nil?
|
90
|
+
end
|
91
|
+
|
83
92
|
def group_by_included?
|
84
93
|
!group_by_index.nil?
|
85
94
|
end
|
@@ -173,7 +182,7 @@ class QueryHelper
|
|
173
182
|
ColumnMap.new(
|
174
183
|
alias_name: sql_alias,
|
175
184
|
sql_expression: sql_expression.squish,
|
176
|
-
aggregate: /(array_agg|avg|bit_and|bit_or|bool_and|bool_or|count|every|json_agg|jsonb_agg|json_object_agg|jsonb_object_agg|max|min|string_agg|sum|xmlagg)\((.*)\)/.match?(sql_expression)
|
185
|
+
aggregate: /(array_agg|avg|bit_and|bit_or|bool_and|bool_or|boolor_agg|booland_agg|count|every|json_agg|jsonb_agg|json_object_agg|jsonb_object_agg|max|min|string_agg|sum|xmlagg)\((.*)\)/.match?(sql_expression)
|
177
186
|
) if sql_alias
|
178
187
|
end
|
179
188
|
column_maps.compact
|
@@ -3,17 +3,18 @@ require "query_helper/invalid_query_error"
|
|
3
3
|
class QueryHelper
|
4
4
|
class SqlSort
|
5
5
|
|
6
|
-
attr_accessor :column_maps, :select_strings, :sort_tiebreak
|
6
|
+
attr_accessor :column_maps, :select_strings, :sort_tiebreak, :column_sort_order
|
7
7
|
|
8
|
-
def initialize(sort_string: "", sort_tiebreak: "", column_maps: [])
|
8
|
+
def initialize(sort_string: "", sort_tiebreak: "", column_maps: [], column_sort_order: {})
|
9
9
|
@sort_string = sort_string
|
10
10
|
@column_maps = column_maps
|
11
11
|
@sort_tiebreak = sort_tiebreak
|
12
|
+
@column_sort_order = column_sort_order
|
12
13
|
@select_strings = []
|
13
14
|
end
|
14
15
|
|
15
16
|
def parse_sort_string
|
16
|
-
return [] if @sort_string.blank? && @sort_tiebreak.blank?
|
17
|
+
return [] if @sort_string.blank? && @sort_tiebreak.blank? && @column_sort_order.blank?
|
17
18
|
|
18
19
|
return attributes_sql_expression(@sort_tiebreak) if @sort_string.blank?
|
19
20
|
|
@@ -25,38 +26,55 @@ class QueryHelper
|
|
25
26
|
|
26
27
|
def attributes_sql_expression(sort_attribute)
|
27
28
|
sql_strings = []
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
29
|
+
if sort_attribute.present?
|
30
|
+
sorts = sort_attribute.split(",")
|
31
|
+
sorts.each_with_index do |sort, index|
|
32
|
+
sort_alias = sort.split(":")[0]
|
33
|
+
direction = sort.split(":")[1]
|
34
|
+
modifier = sort.split(":")[2]
|
35
|
+
begin
|
36
|
+
sql_expression = @column_maps.find{ |m| m.alias_name.casecmp?(sort_alias) }.sql_expression
|
37
|
+
rescue NoMethodError => e
|
38
|
+
raise InvalidQueryError.new("Sorting not allowed on column '#{sort_alias}'")
|
39
|
+
end
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
41
|
+
if direction == "desc"
|
42
|
+
case ActiveRecord::Base.connection.adapter_name
|
43
|
+
when "SQLite" # SQLite is used in the test suite
|
44
|
+
direction = "desc"
|
45
|
+
else
|
46
|
+
direction = "desc nulls last"
|
47
|
+
end
|
43
48
|
else
|
44
|
-
direction = "
|
49
|
+
direction = "asc"
|
45
50
|
end
|
46
|
-
else
|
47
|
-
direction = "asc"
|
48
|
-
end
|
49
51
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
52
|
+
case modifier
|
53
|
+
when "lowercase"
|
54
|
+
sql_expression = "lower(#{sql_expression})"
|
55
|
+
# When select distincts are used, the order by clause must be included in the select clause
|
56
|
+
@select_strings << sql_expression
|
57
|
+
end
|
56
58
|
|
57
|
-
|
59
|
+
sql_strings << "#{sql_expression} #{direction}"
|
60
|
+
end
|
58
61
|
end
|
62
|
+
sql_strings << parse_custom_sort_string if @column_sort_order.present? && @column_sort_order[:sort_values].present?
|
59
63
|
sql_strings
|
60
64
|
end
|
65
|
+
|
66
|
+
# This method is used for sorting enum based column
|
67
|
+
def parse_custom_sort_string
|
68
|
+
sort_column = @column_sort_order[:column_name]
|
69
|
+
sort_values = @column_sort_order[:sort_values]
|
70
|
+
direction = @column_sort_order[:direction]
|
71
|
+
|
72
|
+
sql_expression = '(CASE'
|
73
|
+
sort_values.each_with_index do |value, index|
|
74
|
+
sql_expression << " WHEN #{sort_column}=#{value} THEN #{index}"
|
75
|
+
end
|
76
|
+
sql_expression << " END) #{direction}"
|
77
|
+
sql_expression
|
78
|
+
end
|
61
79
|
end
|
62
80
|
end
|
data/lib/query_helper/version.rb
CHANGED
data/lib/query_helper.rb
CHANGED
@@ -67,6 +67,7 @@ class QueryHelper
|
|
67
67
|
sql_filter: nil,
|
68
68
|
sql_sort: nil,
|
69
69
|
sort_tiebreak: nil,
|
70
|
+
column_sort_order: nil,
|
70
71
|
page: nil,
|
71
72
|
per_page: nil,
|
72
73
|
search_string: nil,
|
@@ -85,6 +86,7 @@ class QueryHelper
|
|
85
86
|
@sql_filter = sql_filter if sql_filter
|
86
87
|
@sql_sort = sql_sort if sql_sort
|
87
88
|
@sql_sort.sort_tiebreak = sort_tiebreak if sort_tiebreak
|
89
|
+
@sql_sort.column_sort_order = column_sort_order if column_sort_order
|
88
90
|
@search_string = search_string if search_string
|
89
91
|
@page = determine_page(page: page, per_page: per_page) if page
|
90
92
|
@per_page = determine_per_page(page: page, per_page: per_page) if per_page
|
@@ -119,6 +121,7 @@ class QueryHelper
|
|
119
121
|
|
120
122
|
having_clauses = @sql_filter.having_clauses
|
121
123
|
where_clauses = @sql_filter.where_clauses
|
124
|
+
qualify_clauses = @sql_filter.qualify_clauses
|
122
125
|
|
123
126
|
if @search_string
|
124
127
|
search_filter = search_filter(column_maps)
|
@@ -138,6 +141,7 @@ class QueryHelper
|
|
138
141
|
sql: @query,
|
139
142
|
where_clauses: where_clauses,
|
140
143
|
having_clauses: having_clauses,
|
144
|
+
qualify_clauses: qualify_clauses,
|
141
145
|
order_by_clauses: @sql_sort.parse_sort_string,
|
142
146
|
include_limit_clause: @page && @per_page ? true : false,
|
143
147
|
additional_select_clauses: @sql_sort.select_strings
|
data/query_helper.gemspec
CHANGED
@@ -35,11 +35,11 @@ Gem::Specification.new do |spec|
|
|
35
35
|
spec.bindir = "exe"
|
36
36
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
37
37
|
spec.require_paths = ["lib"]
|
38
|
+
spec.required_ruby_version = ">= 2.6.0"
|
38
39
|
|
39
|
-
spec.add_development_dependency "bundler", "~> 2.2.
|
40
|
+
spec.add_development_dependency "bundler", "~> 2.2.27"
|
40
41
|
spec.add_development_dependency "rake", "~> 13.0"
|
41
42
|
spec.add_development_dependency "rspec", "~> 3.0"
|
42
|
-
spec.add_development_dependency "sqlite3", "~> 1.3.6"
|
43
43
|
spec.add_development_dependency "faker", "~> 1.9.3"
|
44
44
|
spec.add_development_dependency "byebug"
|
45
45
|
spec.add_development_dependency 'rspec-rails'
|
@@ -48,4 +48,5 @@ Gem::Specification.new do |spec|
|
|
48
48
|
|
49
49
|
spec.add_dependency "activerecord", "> 5"
|
50
50
|
spec.add_dependency "activesupport", "> 5"
|
51
|
+
spec.add_dependency "sqlite3"
|
51
52
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: query_helper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.28
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evan McDaniel
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-04-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 2.2.
|
19
|
+
version: 2.2.27
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 2.2.
|
26
|
+
version: 2.2.27
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,20 +52,6 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '3.0'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: sqlite3
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - "~>"
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: 1.3.6
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - "~>"
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: 1.3.6
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: faker
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -164,6 +150,20 @@ dependencies:
|
|
164
150
|
- - ">"
|
165
151
|
- !ruby/object:Gem::Version
|
166
152
|
version: '5'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: sqlite3
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :runtime
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
167
|
description: Ruby gem developed to help with pagination, filtering, sorting, and including
|
168
168
|
associations on both active record queries and custom sql queries
|
169
169
|
email:
|
@@ -172,9 +172,9 @@ executables: []
|
|
172
172
|
extensions: []
|
173
173
|
extra_rdoc_files: []
|
174
174
|
files:
|
175
|
+
- ".github/workflows/ci.yml"
|
175
176
|
- ".gitignore"
|
176
177
|
- ".rspec"
|
177
|
-
- ".travis.yml"
|
178
178
|
- CODE_OF_CONDUCT.md
|
179
179
|
- Gemfile
|
180
180
|
- Gemfile.lock
|
@@ -199,7 +199,7 @@ homepage: https://github.com/iserve-products/query_helper
|
|
199
199
|
licenses:
|
200
200
|
- MIT
|
201
201
|
metadata: {}
|
202
|
-
post_install_message:
|
202
|
+
post_install_message:
|
203
203
|
rdoc_options: []
|
204
204
|
require_paths:
|
205
205
|
- lib
|
@@ -207,7 +207,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
207
207
|
requirements:
|
208
208
|
- - ">="
|
209
209
|
- !ruby/object:Gem::Version
|
210
|
-
version:
|
210
|
+
version: 2.6.0
|
211
211
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
212
212
|
requirements:
|
213
213
|
- - ">="
|
@@ -215,7 +215,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
215
215
|
version: '0'
|
216
216
|
requirements: []
|
217
217
|
rubygems_version: 3.1.4
|
218
|
-
signing_key:
|
218
|
+
signing_key:
|
219
219
|
specification_version: 4
|
220
220
|
summary: Ruby Gem to help with pagination and data formatting at Pattern, Inc.
|
221
221
|
test_files: []
|