query_filter 0.1.5 → 0.2.2
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 +5 -5
- data/.gitignore +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +1 -1
- data/Gemfile.lock +31 -70
- data/README.md +104 -0
- data/lib/query_filter.rb +13 -0
- data/lib/query_filter/base.rb +2 -0
- data/lib/query_filter/rspec_matchers.rb +65 -0
- data/lib/query_filter/rules/date_range.rb +31 -26
- data/lib/query_filter/rules/order_by.rb +18 -14
- data/lib/query_filter/rules/range.rb +30 -26
- data/lib/query_filter/rules/scope.rb +3 -1
- data/lib/query_filter/rules/splitter_range.rb +35 -30
- data/lib/query_filter/utils/date_normalizer.rb +59 -0
- data/lib/query_filter/utils/date_period.rb +31 -20
- data/lib/query_filter/utils/scope_range.rb +37 -33
- data/lib/query_filter/utils/user_conditions.rb +4 -3
- data/lib/query_filter/version.rb +3 -1
- data/query_filter.gemspec +3 -6
- metadata +22 -53
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 6fe7cf9e804b940444eefcf3fbced4b24099d52d32f71d896bcb401fcb1e9e43
|
|
4
|
+
data.tar.gz: e61f5e207c0a6c7fccae4d44a6a5990cde48f2432d0f520ea47767a417323504
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: caa899332d0b1034baf784a418c1788d70220380d783069edf8b653729d47bdacd67016828546dfe41a078e0000e72b54379b5f60a67f5bf1e4823f4e8e72862
|
|
7
|
+
data.tar.gz: 931eed522f0e6d00cab63631967f70729f5d22067016d12a89220d34ee38513213c47ced8090c1062e10f9d0001490dffdc9913b9cc3a0dc84c4395e3bd4e2b0
|
data/.gitignore
CHANGED
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.5.5
|
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,84 +1,47 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
query_filter (0.
|
|
4
|
+
query_filter (0.2.2)
|
|
5
5
|
activesupport (>= 4.0)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
8
8
|
remote: https://rubygems.org/
|
|
9
9
|
specs:
|
|
10
|
-
activemodel (
|
|
11
|
-
activesupport (=
|
|
12
|
-
activerecord (
|
|
13
|
-
activemodel (=
|
|
14
|
-
activesupport (=
|
|
15
|
-
|
|
16
|
-
activesupport (5.1.2)
|
|
10
|
+
activemodel (6.0.3.2)
|
|
11
|
+
activesupport (= 6.0.3.2)
|
|
12
|
+
activerecord (6.0.3.2)
|
|
13
|
+
activemodel (= 6.0.3.2)
|
|
14
|
+
activesupport (= 6.0.3.2)
|
|
15
|
+
activesupport (6.0.3.2)
|
|
17
16
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
18
|
-
i18n (
|
|
17
|
+
i18n (>= 0.7, < 2)
|
|
19
18
|
minitest (~> 5.1)
|
|
20
19
|
tzinfo (~> 1.1)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
thor (>= 0.18.1)
|
|
36
|
-
guard-compat (1.2.1)
|
|
37
|
-
guard-rspec (4.7.3)
|
|
38
|
-
guard (~> 2.1)
|
|
39
|
-
guard-compat (~> 1.1)
|
|
40
|
-
rspec (>= 2.99.0, < 4.0)
|
|
41
|
-
i18n (0.8.4)
|
|
42
|
-
listen (3.1.5)
|
|
43
|
-
rb-fsevent (~> 0.9, >= 0.9.4)
|
|
44
|
-
rb-inotify (~> 0.9, >= 0.9.7)
|
|
45
|
-
ruby_dep (~> 1.2)
|
|
46
|
-
lumberjack (1.0.12)
|
|
47
|
-
method_source (0.8.2)
|
|
48
|
-
minitest (5.10.2)
|
|
49
|
-
nenv (0.3.0)
|
|
50
|
-
notiffany (0.1.1)
|
|
51
|
-
nenv (~> 0.1)
|
|
52
|
-
shellany (~> 0.0)
|
|
53
|
-
pry (0.10.4)
|
|
54
|
-
coderay (~> 1.1.0)
|
|
55
|
-
method_source (~> 0.8.1)
|
|
56
|
-
slop (~> 3.4)
|
|
57
|
-
rake (10.5.0)
|
|
58
|
-
rb-fsevent (0.9.8)
|
|
59
|
-
rb-inotify (0.9.10)
|
|
60
|
-
ffi (>= 0.5.0, < 2)
|
|
61
|
-
rspec (3.6.0)
|
|
62
|
-
rspec-core (~> 3.6.0)
|
|
63
|
-
rspec-expectations (~> 3.6.0)
|
|
64
|
-
rspec-mocks (~> 3.6.0)
|
|
65
|
-
rspec-core (3.6.0)
|
|
66
|
-
rspec-support (~> 3.6.0)
|
|
67
|
-
rspec-expectations (3.6.0)
|
|
20
|
+
zeitwerk (~> 2.2, >= 2.2.2)
|
|
21
|
+
concurrent-ruby (1.1.6)
|
|
22
|
+
diff-lcs (1.4.2)
|
|
23
|
+
i18n (1.8.3)
|
|
24
|
+
concurrent-ruby (~> 1.0)
|
|
25
|
+
minitest (5.14.1)
|
|
26
|
+
rake (13.0.1)
|
|
27
|
+
rspec (3.9.0)
|
|
28
|
+
rspec-core (~> 3.9.0)
|
|
29
|
+
rspec-expectations (~> 3.9.0)
|
|
30
|
+
rspec-mocks (~> 3.9.0)
|
|
31
|
+
rspec-core (3.9.2)
|
|
32
|
+
rspec-support (~> 3.9.3)
|
|
33
|
+
rspec-expectations (3.9.2)
|
|
68
34
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
69
|
-
rspec-support (~> 3.
|
|
70
|
-
rspec-mocks (3.
|
|
35
|
+
rspec-support (~> 3.9.0)
|
|
36
|
+
rspec-mocks (3.9.1)
|
|
71
37
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
72
|
-
rspec-support (~> 3.
|
|
73
|
-
rspec-support (3.
|
|
74
|
-
|
|
75
|
-
shellany (0.0.1)
|
|
76
|
-
slop (3.6.0)
|
|
77
|
-
sqlite3 (1.3.13)
|
|
78
|
-
thor (0.19.4)
|
|
38
|
+
rspec-support (~> 3.9.0)
|
|
39
|
+
rspec-support (3.9.3)
|
|
40
|
+
sqlite3 (1.4.2)
|
|
79
41
|
thread_safe (0.3.6)
|
|
80
|
-
tzinfo (1.2.
|
|
42
|
+
tzinfo (1.2.7)
|
|
81
43
|
thread_safe (~> 0.1)
|
|
44
|
+
zeitwerk (2.3.0)
|
|
82
45
|
|
|
83
46
|
PLATFORMS
|
|
84
47
|
ruby
|
|
@@ -86,12 +49,10 @@ PLATFORMS
|
|
|
86
49
|
DEPENDENCIES
|
|
87
50
|
activerecord (>= 4.0)
|
|
88
51
|
bundler (~> 1.15)
|
|
89
|
-
guard-rspec (~> 4.7, >= 4.7.3)
|
|
90
52
|
query_filter!
|
|
91
|
-
rake (
|
|
92
|
-
rb-fsevent (= 0.9.8)
|
|
53
|
+
rake (>= 12.3.3)
|
|
93
54
|
rspec (~> 3.0)
|
|
94
55
|
sqlite3 (~> 1.3, >= 1.3.13)
|
|
95
56
|
|
|
96
57
|
BUNDLED WITH
|
|
97
|
-
1.
|
|
58
|
+
1.17.3
|
data/README.md
CHANGED
|
@@ -26,6 +26,37 @@ class Order < ActiveRecord::Base
|
|
|
26
26
|
end
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
Where OrderFilter class looks like:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
# app/filters/order_filter.rb
|
|
33
|
+
#
|
|
34
|
+
class OrderFilter < QueryFilter::Base
|
|
35
|
+
scope :customer_id
|
|
36
|
+
scope :service
|
|
37
|
+
|
|
38
|
+
range :total
|
|
39
|
+
|
|
40
|
+
date_range :completed_at
|
|
41
|
+
|
|
42
|
+
def scope_customer_id(value)
|
|
43
|
+
query.where(customer_id: value)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def scope_service(value)
|
|
47
|
+
query.where(service_id: value.to_i)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def range_total(range)
|
|
51
|
+
query.where(range.query('orders.total'))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def date_range_completed_at(period)
|
|
55
|
+
query.where(completed_at: period.range_original)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
```
|
|
59
|
+
|
|
29
60
|
## Installation
|
|
30
61
|
|
|
31
62
|
Add this line to your application's Gemfile:
|
|
@@ -94,6 +125,23 @@ This gem support next types of filter params:
|
|
|
94
125
|
orders.created_at BETWEEN '2017-06-24 04:00:00.000000' AND '2017-07-01 03:59:59.999999'
|
|
95
126
|
```
|
|
96
127
|
|
|
128
|
+
When we have date with custom time:
|
|
129
|
+
```ruby
|
|
130
|
+
date_range :range, format: '%m/%d/%Y %H:%M'
|
|
131
|
+
|
|
132
|
+
def date_range_range(period)
|
|
133
|
+
query.where(created_at: period.range)
|
|
134
|
+
end
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`period.range` will ignore time and always return time start_of_day and end_of_day, but the method `period.range_original` will return dates without time modification
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
def date_range_range(period)
|
|
141
|
+
query.where(created_at: period.range_original)
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
97
145
|
* order by
|
|
98
146
|
```ruby
|
|
99
147
|
order_by :sort_column, via: :sort_mode
|
|
@@ -188,6 +236,62 @@ QueryFilter.setup do |config|
|
|
|
188
236
|
end
|
|
189
237
|
```
|
|
190
238
|
|
|
239
|
+
## Testing
|
|
240
|
+
|
|
241
|
+
query_filter gem defines some custom RSpec matchers. Include them to your project:
|
|
242
|
+
|
|
243
|
+
```ruby
|
|
244
|
+
# spec/support/query_filter.rb
|
|
245
|
+
|
|
246
|
+
require "query_filter/rspec_matchers"
|
|
247
|
+
|
|
248
|
+
RSpec.configure do |config|
|
|
249
|
+
config.include QueryFilter::RSpecMatchers, type: :query_filter
|
|
250
|
+
end
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Custom matchers will be available to use in specs:
|
|
254
|
+
|
|
255
|
+
```ruby
|
|
256
|
+
# spec/filters/order_filter_spec.rb
|
|
257
|
+
|
|
258
|
+
describe OrderFilter, type: :query_filter do
|
|
259
|
+
context "scope deleted" do
|
|
260
|
+
it "performs query" do
|
|
261
|
+
expect { filter(deleted: true) }.to perform_query(deleted: true)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
it "doesn't perform query with wrong params" do
|
|
265
|
+
expect { filter(deleted: "invalid_param") }.to_not perform_query
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
it "performs query by state" do
|
|
270
|
+
expect(relation).to receive(:with_state).with(:pending)
|
|
271
|
+
filter(state: :pending)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
it "reorders by sort_column" do
|
|
275
|
+
expect { filter(sort_column: :id, sort_mode: :desc) }
|
|
276
|
+
.to reorder.by("orders.id DESC NULLS LAST")
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
context "query against the database" do
|
|
280
|
+
let(:order) { create(:order, state: :pending) }
|
|
281
|
+
|
|
282
|
+
# relation is a double by default, but can be redefined to an actual ActiveRecord::Relation:
|
|
283
|
+
before do
|
|
284
|
+
relation Order.all
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
it "finds the order" do
|
|
288
|
+
expect(filter(state: :pending)).to contain_exactly(order)
|
|
289
|
+
expect(filter(state: :invalid_state)).to be_empty
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
```
|
|
294
|
+
|
|
191
295
|
## Development
|
|
192
296
|
|
|
193
297
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/query_filter.rb
CHANGED
|
@@ -17,6 +17,7 @@ module QueryFilter
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
module Utils
|
|
20
|
+
autoload :DateNormalizer, 'query_filter/utils/date_normalizer'
|
|
20
21
|
autoload :DatePeriod, 'query_filter/utils/date_period'
|
|
21
22
|
autoload :ScopeRange, 'query_filter/utils/scope_range'
|
|
22
23
|
autoload :UserConditions, 'query_filter/utils/user_conditions'
|
|
@@ -30,6 +31,18 @@ module QueryFilter
|
|
|
30
31
|
mattr_accessor :date_period_splitter
|
|
31
32
|
self.date_period_splitter = 'to'
|
|
32
33
|
|
|
34
|
+
mattr_accessor :date_display_format
|
|
35
|
+
self.date_display_format = '%Y-%m-%d %H:%M'
|
|
36
|
+
|
|
37
|
+
mattr_accessor :datetime_formats
|
|
38
|
+
self.datetime_formats = %w[
|
|
39
|
+
%Y-%m-%dT%H:%M:%S.%L%z
|
|
40
|
+
%Y-%m-%dT%H:%M:%S%z
|
|
41
|
+
%Y-%m-%d %H:%M:%S
|
|
42
|
+
%Y-%m-%d %H:%M
|
|
43
|
+
%Y-%m-%d
|
|
44
|
+
]
|
|
45
|
+
|
|
33
46
|
# Default way to setup QueryFilter
|
|
34
47
|
# @example
|
|
35
48
|
# QueryFilter.setup do |config|
|
data/lib/query_filter/base.rb
CHANGED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.configure do |config|
|
|
4
|
+
module QueryFilter::RSpecMatchers
|
|
5
|
+
extend RSpec::Matchers::DSL
|
|
6
|
+
|
|
7
|
+
def relation(override = nil)
|
|
8
|
+
@relation ||= override || double('ActiveRecord::Relation')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def filter(params)
|
|
12
|
+
described_class.new(relation, params).to_query
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Usage:
|
|
16
|
+
#
|
|
17
|
+
# expect { filter(username: "joe") }.to perform_query(users: { username: "joe" })
|
|
18
|
+
# expect { filter(field: :invalid_value) }.to_not perform_query
|
|
19
|
+
#
|
|
20
|
+
matcher :perform_query do |*params_for_where|
|
|
21
|
+
def supports_block_expectations?
|
|
22
|
+
true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
match do |block|
|
|
26
|
+
expect(relation).to receive(:where).with(*params_for_where)
|
|
27
|
+
block.call
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
match_when_negated do |block|
|
|
31
|
+
expect(relation).to_not receive(:where)
|
|
32
|
+
block.call
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Usage:
|
|
37
|
+
#
|
|
38
|
+
# expect { filter(sort: :asc) }.to reorder.by("users.created_at" => "asc")
|
|
39
|
+
# expect { filter(sort: :invalid_value) }.to_not reorder
|
|
40
|
+
#
|
|
41
|
+
matcher :reorder do
|
|
42
|
+
def supports_block_expectations?
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
match do |block|
|
|
47
|
+
expect(relation).to receive(:reorder).with(@params_for_reorder)
|
|
48
|
+
block.call
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
match_when_negated do |block|
|
|
52
|
+
expect(relation).to_not receive(:reorder)
|
|
53
|
+
block.call
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
chain :by do |conditions|
|
|
57
|
+
@params_for_reorder = conditions
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
config.after do
|
|
63
|
+
@relation = nil
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -1,42 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Parse date range params
|
|
2
4
|
#
|
|
3
5
|
# date_range :created_at, keys: [:start_date, :end_date]
|
|
4
6
|
#
|
|
5
7
|
# date_range :last_login_date
|
|
6
8
|
#
|
|
7
|
-
module QueryFilter
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
module QueryFilter
|
|
10
|
+
module Rules
|
|
11
|
+
class DateRange < Scope
|
|
12
|
+
def name
|
|
13
|
+
'date_range'
|
|
14
|
+
end
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
def valid?(values)
|
|
17
|
+
period = build_period_from_params(values)
|
|
18
|
+
!(period.nil? || period.default?)
|
|
19
|
+
end
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
def normalize_params(values)
|
|
22
|
+
build_period_from_params(values)
|
|
23
|
+
end
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
protected
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
def build_period_from_params(params)
|
|
28
|
+
if params[key].present?
|
|
29
|
+
QueryFilter::Utils::DatePeriod.parse_from_string(params[key], @options[:format])
|
|
30
|
+
elsif keys_start_end_dates_exists?(params)
|
|
31
|
+
QueryFilter::Utils::DatePeriod.new(*values_start_end_dates(params), @options[:format])
|
|
32
|
+
end
|
|
29
33
|
end
|
|
30
|
-
end
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
def keys_start_end_dates_exists?(params)
|
|
36
|
+
values = values_start_end_dates(params)
|
|
37
|
+
!values.nil? && values.size == 2
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def values_start_end_dates(params)
|
|
41
|
+
return unless @options[:keys]
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
params.values_at(*@options[:keys])
|
|
43
|
+
params.values_at(*@options[:keys])
|
|
44
|
+
end
|
|
40
45
|
end
|
|
41
46
|
end
|
|
42
47
|
end
|
|
@@ -1,23 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Define order by filter rule
|
|
2
4
|
#
|
|
3
|
-
module QueryFilter
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
module QueryFilter
|
|
6
|
+
module Rules
|
|
7
|
+
class OrderBy < Scope
|
|
8
|
+
DIRECTIONS = %w[asc desc].freeze
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
def name
|
|
11
|
+
'order_by'
|
|
12
|
+
end
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
def valid?(params)
|
|
15
|
+
params[key].present? && DIRECTIONS.include?(params[direction_key].try(:downcase))
|
|
16
|
+
end
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
def direction_key
|
|
19
|
+
@direction_key ||= (@options[:via] || 'sort_direction').to_sym
|
|
20
|
+
end
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
def normalize_params(values)
|
|
23
|
+
[values[key], values[direction_key]]
|
|
24
|
+
end
|
|
21
25
|
end
|
|
22
26
|
end
|
|
23
27
|
end
|
|
@@ -1,38 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Define range filter rule
|
|
2
4
|
#
|
|
3
|
-
module QueryFilter
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
module QueryFilter
|
|
6
|
+
module Rules
|
|
7
|
+
class Range < Scope
|
|
8
|
+
def initialize(keys, options = {})
|
|
9
|
+
@key = Array(keys).first
|
|
10
|
+
@keys = [key_from, key_to]
|
|
11
|
+
@options = options
|
|
12
|
+
end
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
def name
|
|
15
|
+
'range'
|
|
16
|
+
end
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
def valid?(values)
|
|
19
|
+
filter = build_range_from_params(values)
|
|
20
|
+
filter.valid?
|
|
21
|
+
end
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
def normalize_params(values)
|
|
24
|
+
build_range_from_params(values)
|
|
25
|
+
end
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
protected
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
def build_range_from_params(params)
|
|
30
|
+
QueryFilter::Utils::ScopeRange.new(key, params)
|
|
31
|
+
end
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
def key_from
|
|
34
|
+
@key_from ||= "#{key}_from".to_sym
|
|
35
|
+
end
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
def key_to
|
|
38
|
+
@key_to ||= "#{key}_to".to_sym
|
|
39
|
+
end
|
|
36
40
|
end
|
|
37
41
|
end
|
|
38
42
|
end
|
|
@@ -1,44 +1,49 @@
|
|
|
1
|
-
|
|
2
|
-
class SplitterRange < Scope
|
|
3
|
-
class RangeParam
|
|
4
|
-
SPLITTER = /[;-]/
|
|
1
|
+
# frozen_string_literal: true
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
module QueryFilter
|
|
4
|
+
module Rules
|
|
5
|
+
class SplitterRange < Scope
|
|
6
|
+
class RangeParam
|
|
7
|
+
SPLITTER = /[;-]/.freeze
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
def initialize(value)
|
|
10
|
+
@value = value
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def valid?
|
|
14
|
+
@value.present? && items.size == 2
|
|
15
|
+
end
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
def range
|
|
18
|
+
::Range.new(*items)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def items
|
|
22
|
+
@items ||= @value.split(SPLITTER).map(&:strip).map(&:to_i)
|
|
23
|
+
end
|
|
16
24
|
end
|
|
17
25
|
|
|
18
|
-
def
|
|
19
|
-
|
|
26
|
+
def name
|
|
27
|
+
'splitter_range'
|
|
20
28
|
end
|
|
21
|
-
end
|
|
22
29
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
def valid?(values)
|
|
31
|
+
period = build_period_from_params(values)
|
|
32
|
+
!period.nil?
|
|
33
|
+
end
|
|
26
34
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
end
|
|
35
|
+
def normalize_params(values)
|
|
36
|
+
build_period_from_params(values)
|
|
37
|
+
end
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
build_period_from_params(values)
|
|
34
|
-
end
|
|
39
|
+
protected
|
|
35
40
|
|
|
36
|
-
|
|
41
|
+
def build_period_from_params(values)
|
|
42
|
+
param = RangeParam.new(values[key])
|
|
43
|
+
return unless param.valid?
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return unless param.valid?
|
|
41
|
-
param
|
|
45
|
+
param
|
|
46
|
+
end
|
|
42
47
|
end
|
|
43
48
|
end
|
|
44
49
|
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module QueryFilter
|
|
4
|
+
module Utils
|
|
5
|
+
class DateNormalizer
|
|
6
|
+
PG_MIN_YEAR = -4713
|
|
7
|
+
PG_MAX_YEAR = 294_276
|
|
8
|
+
|
|
9
|
+
attr_reader :date, :format
|
|
10
|
+
|
|
11
|
+
def initialize(date, format = nil)
|
|
12
|
+
@date = date
|
|
13
|
+
@format = format
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def parsed_value
|
|
17
|
+
@parsed_value ||= parse
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def normalize
|
|
21
|
+
valid?(parsed_value) ? parsed_value : default_date
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def date?
|
|
27
|
+
date.is_a?(Time) || date.is_a?(DateTime)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def valid?(value)
|
|
31
|
+
return false unless Date.valid_date?(value.year, value.month, value.day)
|
|
32
|
+
|
|
33
|
+
value.year > PG_MIN_YEAR && value.year < PG_MAX_YEAR
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def parse
|
|
37
|
+
return date if date?
|
|
38
|
+
return default_date if date.blank?
|
|
39
|
+
|
|
40
|
+
[@format].concat(QueryFilter.datetime_formats).compact.each do |format|
|
|
41
|
+
value = safe_parse_date(date, format)
|
|
42
|
+
return value if value
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
default_date
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def safe_parse_date(string, format)
|
|
49
|
+
DateTime.strptime(string, format)
|
|
50
|
+
rescue ArgumentError => _e
|
|
51
|
+
nil
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def default_date
|
|
55
|
+
Time.zone.today
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -3,33 +3,48 @@
|
|
|
3
3
|
module QueryFilter
|
|
4
4
|
module Utils
|
|
5
5
|
class DatePeriod
|
|
6
|
-
attr_reader :
|
|
7
|
-
|
|
8
|
-
TIME_FORMAT = '%Y-%m-%d %H:%M:%S'
|
|
6
|
+
attr_reader :date_from_raw, :date_to_raw
|
|
9
7
|
|
|
10
8
|
def initialize(date_from = nil, date_to = nil, format = nil)
|
|
11
|
-
@format = (format.blank? ? QueryFilter.date_period_format : format)
|
|
12
9
|
@date_from_raw = date_from
|
|
13
10
|
@date_to_raw = date_to
|
|
14
|
-
|
|
15
|
-
@date_from = normalize_date(date_from).beginning_of_day
|
|
16
|
-
@date_to = normalize_date(date_to).end_of_day
|
|
11
|
+
@format = format
|
|
17
12
|
end
|
|
18
13
|
|
|
19
14
|
def range
|
|
20
15
|
@range ||= date_from..date_to
|
|
21
16
|
end
|
|
22
17
|
|
|
18
|
+
def range_original
|
|
19
|
+
@range_original ||= date_from_original..date_to_original
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def date_from
|
|
23
|
+
@date_from ||= date_from_original.beginning_of_day
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def date_to
|
|
27
|
+
@date_to ||= date_to_original.end_of_day
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def date_from_original
|
|
31
|
+
@date_from_original ||= normalize_date(@date_from_raw)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def date_to_original
|
|
35
|
+
@date_to_original ||= normalize_date(@date_to_raw)
|
|
36
|
+
end
|
|
37
|
+
|
|
23
38
|
def title
|
|
24
39
|
@title ||= to_human
|
|
25
40
|
end
|
|
26
41
|
|
|
27
42
|
def datefrom
|
|
28
|
-
@datefrom ||= I18n.l(
|
|
43
|
+
@datefrom ||= I18n.l(date_from, format: date_display_format)
|
|
29
44
|
end
|
|
30
45
|
|
|
31
46
|
def dateto
|
|
32
|
-
@dateto ||= I18n.l(
|
|
47
|
+
@dateto ||= I18n.l(date_to, format: date_display_format)
|
|
33
48
|
end
|
|
34
49
|
|
|
35
50
|
def to_param
|
|
@@ -55,25 +70,21 @@ module QueryFilter
|
|
|
55
70
|
return value if value.is_a?(DatePeriod)
|
|
56
71
|
|
|
57
72
|
if value.blank?
|
|
58
|
-
new
|
|
73
|
+
new
|
|
59
74
|
else
|
|
60
75
|
dates = value.to_s.split(QueryFilter.date_period_splitter).map(&:strip)
|
|
61
|
-
new(dates[0], dates[1], format)
|
|
76
|
+
new(dates[0], dates[1], format || QueryFilter.date_period_format)
|
|
62
77
|
end
|
|
63
78
|
end
|
|
64
79
|
|
|
65
80
|
private
|
|
66
81
|
|
|
82
|
+
def date_display_format
|
|
83
|
+
@format || QueryFilter.date_display_format
|
|
84
|
+
end
|
|
85
|
+
|
|
67
86
|
def normalize_date(date)
|
|
68
|
-
|
|
69
|
-
return Time.zone.today if date.blank?
|
|
70
|
-
|
|
71
|
-
begin
|
|
72
|
-
time = DateTime.strptime(date, @format)
|
|
73
|
-
Time.zone.parse(time.strftime(TIME_FORMAT))
|
|
74
|
-
rescue ArgumentError => _e
|
|
75
|
-
Time.zone.today
|
|
76
|
-
end
|
|
87
|
+
QueryFilter::Utils::DateNormalizer.new(date, @format).normalize
|
|
77
88
|
end
|
|
78
89
|
end
|
|
79
90
|
end
|
|
@@ -1,42 +1,46 @@
|
|
|
1
|
-
|
|
2
|
-
# Usage:
|
|
3
|
-
#
|
|
4
|
-
# range = Utils::ScopeRange.new(:orders, { orders_from: 1, orders_to: 44 })
|
|
5
|
-
# range.query('orders_count')
|
|
6
|
-
#
|
|
7
|
-
class ScopeRange
|
|
8
|
-
def initialize(name, options = {})
|
|
9
|
-
@name = name
|
|
10
|
-
@options = options
|
|
11
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
12
2
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
3
|
+
module QueryFilter
|
|
4
|
+
module Utils
|
|
5
|
+
# Usage:
|
|
6
|
+
#
|
|
7
|
+
# range = Utils::ScopeRange.new(:orders, { orders_from: 1, orders_to: 44 })
|
|
8
|
+
# range.query('orders_count')
|
|
9
|
+
#
|
|
10
|
+
class ScopeRange
|
|
11
|
+
def initialize(name, options = {})
|
|
12
|
+
@name = name
|
|
13
|
+
@options = options
|
|
14
|
+
end
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
def name_from
|
|
17
|
+
@name_from ||= "#{@name}_from".to_sym
|
|
18
|
+
end
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
def name_to
|
|
21
|
+
@name_to ||= "#{@name}_to".to_sym
|
|
22
|
+
end
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
def value_from
|
|
25
|
+
@options[name_from]
|
|
26
|
+
end
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
def value_to
|
|
29
|
+
@options[name_to]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def valid?
|
|
33
|
+
@options && (value_from.present? || value_to.present?)
|
|
34
|
+
end
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
def query(column)
|
|
37
|
+
if value_from.present? && value_to.present?
|
|
38
|
+
["#{column} BETWEEN ? AND ?", value_from, value_to]
|
|
39
|
+
elsif value_from.present?
|
|
40
|
+
["#{column} >= ?", value_from]
|
|
41
|
+
elsif value_to.present?
|
|
42
|
+
["#{column} <= ?", value_to]
|
|
43
|
+
end
|
|
40
44
|
end
|
|
41
45
|
end
|
|
42
46
|
end
|
|
@@ -15,8 +15,8 @@ module QueryFilter
|
|
|
15
15
|
|
|
16
16
|
def passed?
|
|
17
17
|
return true if empty?
|
|
18
|
-
value = target.params
|
|
19
18
|
|
|
19
|
+
value = target.params
|
|
20
20
|
conditions_lambdas.all? { |c| c.call(target, value) }
|
|
21
21
|
end
|
|
22
22
|
|
|
@@ -35,8 +35,8 @@ module QueryFilter
|
|
|
35
35
|
|
|
36
36
|
private
|
|
37
37
|
|
|
38
|
-
def invert_lambda(
|
|
39
|
-
->(*args, &blk) { !
|
|
38
|
+
def invert_lambda(lblock)
|
|
39
|
+
->(*args, &blk) { !lblock.call(*args, &blk) }
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
# Filters support:
|
|
@@ -61,6 +61,7 @@ module QueryFilter
|
|
|
61
61
|
if filter.arity > 1
|
|
62
62
|
lambda do |target, _, &block|
|
|
63
63
|
raise ArgumentError unless block
|
|
64
|
+
|
|
64
65
|
target.instance_exec(target, block, &filter)
|
|
65
66
|
end
|
|
66
67
|
elsif filter.arity <= 0
|
data/lib/query_filter/version.rb
CHANGED
data/query_filter.gemspec
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
# coding: utf-8
|
|
3
2
|
|
|
4
|
-
lib = File.expand_path('
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
5
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
6
5
|
require 'query_filter/version'
|
|
7
6
|
|
|
@@ -23,12 +22,10 @@ Gem::Specification.new do |spec|
|
|
|
23
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
24
23
|
spec.require_paths = ['lib']
|
|
25
24
|
|
|
25
|
+
spec.add_development_dependency 'activerecord', '>= 4.0'
|
|
26
26
|
spec.add_development_dependency 'bundler', '~> 1.15'
|
|
27
|
-
spec.add_development_dependency 'rake', '
|
|
27
|
+
spec.add_development_dependency 'rake', '>= 12.3.3'
|
|
28
28
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
29
|
-
spec.add_development_dependency 'guard-rspec', '~> 4.7', '>= 4.7.3'
|
|
30
|
-
spec.add_development_dependency 'rb-fsevent', '0.9.8'
|
|
31
|
-
spec.add_development_dependency 'activerecord', '>= 4.0'
|
|
32
29
|
spec.add_development_dependency 'sqlite3', '~> 1.3', '>= 1.3.13'
|
|
33
30
|
|
|
34
31
|
spec.add_dependency 'activesupport', '>= 4.0'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: query_filter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Igor Galeta
|
|
@@ -9,98 +9,64 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: exe
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date:
|
|
12
|
+
date: 2020-06-24 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
|
-
name:
|
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
|
17
|
-
requirements:
|
|
18
|
-
- - "~>"
|
|
19
|
-
- !ruby/object:Gem::Version
|
|
20
|
-
version: '1.15'
|
|
21
|
-
type: :development
|
|
22
|
-
prerelease: false
|
|
23
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
24
|
-
requirements:
|
|
25
|
-
- - "~>"
|
|
26
|
-
- !ruby/object:Gem::Version
|
|
27
|
-
version: '1.15'
|
|
28
|
-
- !ruby/object:Gem::Dependency
|
|
29
|
-
name: rake
|
|
15
|
+
name: activerecord
|
|
30
16
|
requirement: !ruby/object:Gem::Requirement
|
|
31
17
|
requirements:
|
|
32
|
-
- - "
|
|
18
|
+
- - ">="
|
|
33
19
|
- !ruby/object:Gem::Version
|
|
34
|
-
version: '
|
|
20
|
+
version: '4.0'
|
|
35
21
|
type: :development
|
|
36
22
|
prerelease: false
|
|
37
23
|
version_requirements: !ruby/object:Gem::Requirement
|
|
38
24
|
requirements:
|
|
39
|
-
- - "
|
|
25
|
+
- - ">="
|
|
40
26
|
- !ruby/object:Gem::Version
|
|
41
|
-
version: '
|
|
27
|
+
version: '4.0'
|
|
42
28
|
- !ruby/object:Gem::Dependency
|
|
43
|
-
name:
|
|
29
|
+
name: bundler
|
|
44
30
|
requirement: !ruby/object:Gem::Requirement
|
|
45
31
|
requirements:
|
|
46
32
|
- - "~>"
|
|
47
33
|
- !ruby/object:Gem::Version
|
|
48
|
-
version: '
|
|
34
|
+
version: '1.15'
|
|
49
35
|
type: :development
|
|
50
36
|
prerelease: false
|
|
51
37
|
version_requirements: !ruby/object:Gem::Requirement
|
|
52
38
|
requirements:
|
|
53
39
|
- - "~>"
|
|
54
40
|
- !ruby/object:Gem::Version
|
|
55
|
-
version: '
|
|
41
|
+
version: '1.15'
|
|
56
42
|
- !ruby/object:Gem::Dependency
|
|
57
|
-
name:
|
|
43
|
+
name: rake
|
|
58
44
|
requirement: !ruby/object:Gem::Requirement
|
|
59
45
|
requirements:
|
|
60
|
-
- - "~>"
|
|
61
|
-
- !ruby/object:Gem::Version
|
|
62
|
-
version: '4.7'
|
|
63
46
|
- - ">="
|
|
64
47
|
- !ruby/object:Gem::Version
|
|
65
|
-
version:
|
|
48
|
+
version: 12.3.3
|
|
66
49
|
type: :development
|
|
67
50
|
prerelease: false
|
|
68
51
|
version_requirements: !ruby/object:Gem::Requirement
|
|
69
52
|
requirements:
|
|
70
|
-
- - "~>"
|
|
71
|
-
- !ruby/object:Gem::Version
|
|
72
|
-
version: '4.7'
|
|
73
53
|
- - ">="
|
|
74
54
|
- !ruby/object:Gem::Version
|
|
75
|
-
version:
|
|
76
|
-
- !ruby/object:Gem::Dependency
|
|
77
|
-
name: rb-fsevent
|
|
78
|
-
requirement: !ruby/object:Gem::Requirement
|
|
79
|
-
requirements:
|
|
80
|
-
- - '='
|
|
81
|
-
- !ruby/object:Gem::Version
|
|
82
|
-
version: 0.9.8
|
|
83
|
-
type: :development
|
|
84
|
-
prerelease: false
|
|
85
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
86
|
-
requirements:
|
|
87
|
-
- - '='
|
|
88
|
-
- !ruby/object:Gem::Version
|
|
89
|
-
version: 0.9.8
|
|
55
|
+
version: 12.3.3
|
|
90
56
|
- !ruby/object:Gem::Dependency
|
|
91
|
-
name:
|
|
57
|
+
name: rspec
|
|
92
58
|
requirement: !ruby/object:Gem::Requirement
|
|
93
59
|
requirements:
|
|
94
|
-
- - "
|
|
60
|
+
- - "~>"
|
|
95
61
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '
|
|
62
|
+
version: '3.0'
|
|
97
63
|
type: :development
|
|
98
64
|
prerelease: false
|
|
99
65
|
version_requirements: !ruby/object:Gem::Requirement
|
|
100
66
|
requirements:
|
|
101
|
-
- - "
|
|
67
|
+
- - "~>"
|
|
102
68
|
- !ruby/object:Gem::Version
|
|
103
|
-
version: '
|
|
69
|
+
version: '3.0'
|
|
104
70
|
- !ruby/object:Gem::Dependency
|
|
105
71
|
name: sqlite3
|
|
106
72
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -146,6 +112,7 @@ files:
|
|
|
146
112
|
- ".gitignore"
|
|
147
113
|
- ".rspec"
|
|
148
114
|
- ".rubocop.yml"
|
|
115
|
+
- ".ruby-version"
|
|
149
116
|
- ".travis.yml"
|
|
150
117
|
- Gemfile
|
|
151
118
|
- Gemfile.lock
|
|
@@ -157,11 +124,13 @@ files:
|
|
|
157
124
|
- bin/setup
|
|
158
125
|
- lib/query_filter.rb
|
|
159
126
|
- lib/query_filter/base.rb
|
|
127
|
+
- lib/query_filter/rspec_matchers.rb
|
|
160
128
|
- lib/query_filter/rules/date_range.rb
|
|
161
129
|
- lib/query_filter/rules/order_by.rb
|
|
162
130
|
- lib/query_filter/rules/range.rb
|
|
163
131
|
- lib/query_filter/rules/scope.rb
|
|
164
132
|
- lib/query_filter/rules/splitter_range.rb
|
|
133
|
+
- lib/query_filter/utils/date_normalizer.rb
|
|
165
134
|
- lib/query_filter/utils/date_period.rb
|
|
166
135
|
- lib/query_filter/utils/scope_range.rb
|
|
167
136
|
- lib/query_filter/utils/user_conditions.rb
|
|
@@ -187,7 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
187
156
|
version: '0'
|
|
188
157
|
requirements: []
|
|
189
158
|
rubyforge_project:
|
|
190
|
-
rubygems_version: 2.
|
|
159
|
+
rubygems_version: 2.7.6.2
|
|
191
160
|
signing_key:
|
|
192
161
|
specification_version: 4
|
|
193
162
|
summary: ActiveRecord query filter gem
|