json_api_filter 0.1.1 → 0.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 +4 -4
- data/.gitignore +4 -1
- data/.travis.yml +3 -0
- data/Gemfile.lock +143 -17
- data/README.md +33 -8
- data/json_api_filter.gemspec +5 -1
- data/lib/json_api_filter.rb +29 -8
- data/lib/json_api_filter/auto_join.rb +18 -0
- data/lib/json_api_filter/dispatch.rb +112 -0
- data/lib/json_api_filter/field_filters/base.rb +18 -0
- data/lib/json_api_filter/field_filters/compare.rb +54 -0
- data/lib/json_api_filter/field_filters/matcher.rb +35 -0
- data/lib/json_api_filter/field_filters/pagination.rb +19 -0
- data/lib/json_api_filter/field_filters/searcher.rb +12 -0
- data/lib/json_api_filter/field_filters/sorter.rb +19 -0
- data/lib/json_api_filter/filter_attributes.rb +36 -0
- data/lib/json_api_filter/value_parser.rb +37 -0
- data/lib/json_api_filter/version.rb +1 -1
- metadata +70 -6
- data/lib/json_api_filter/filter_by.rb +0 -29
- data/lib/json_api_filter/filters.rb +0 -24
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a0e5c959706685cf6cc047d4824c7ed0e0c35778b6b952aa3b305e5e0eb40f13
|
|
4
|
+
data.tar.gz: 3a87503e2341ae745d0ec6b5154049c95b0a28b527aeb8903ed000e53685bf3b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fee62197e5d3814185e35f7935b76daba4c96dcabd2e655e250512f3267e8295512fffe16301df3b18b089c4eb5b5cbed1a120793dd60518929aa666ba16b5e2
|
|
7
|
+
data.tar.gz: c183c0a031bac479dfa66baf3cb90a9ef6735719c7dc61a12fcbda50412a0239e5dcd6ca4ebd3ddffae23588f9cd90793123a79cd2a44107723a9f869b5d4b2f
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,39 +1,161 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
json_api_filter (0.
|
|
4
|
+
json_api_filter (0.2)
|
|
5
5
|
activesupport (>= 3.0.0)
|
|
6
6
|
|
|
7
7
|
GEM
|
|
8
8
|
remote: https://rubygems.org/
|
|
9
9
|
specs:
|
|
10
|
-
|
|
10
|
+
actioncable (6.1.3.1)
|
|
11
|
+
actionpack (= 6.1.3.1)
|
|
12
|
+
activesupport (= 6.1.3.1)
|
|
13
|
+
nio4r (~> 2.0)
|
|
14
|
+
websocket-driver (>= 0.6.1)
|
|
15
|
+
actionmailbox (6.1.3.1)
|
|
16
|
+
actionpack (= 6.1.3.1)
|
|
17
|
+
activejob (= 6.1.3.1)
|
|
18
|
+
activerecord (= 6.1.3.1)
|
|
19
|
+
activestorage (= 6.1.3.1)
|
|
20
|
+
activesupport (= 6.1.3.1)
|
|
21
|
+
mail (>= 2.7.1)
|
|
22
|
+
actionmailer (6.1.3.1)
|
|
23
|
+
actionpack (= 6.1.3.1)
|
|
24
|
+
actionview (= 6.1.3.1)
|
|
25
|
+
activejob (= 6.1.3.1)
|
|
26
|
+
activesupport (= 6.1.3.1)
|
|
27
|
+
mail (~> 2.5, >= 2.5.4)
|
|
28
|
+
rails-dom-testing (~> 2.0)
|
|
29
|
+
actionpack (6.1.3.1)
|
|
30
|
+
actionview (= 6.1.3.1)
|
|
31
|
+
activesupport (= 6.1.3.1)
|
|
32
|
+
rack (~> 2.0, >= 2.0.9)
|
|
33
|
+
rack-test (>= 0.6.3)
|
|
34
|
+
rails-dom-testing (~> 2.0)
|
|
35
|
+
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
|
36
|
+
actiontext (6.1.3.1)
|
|
37
|
+
actionpack (= 6.1.3.1)
|
|
38
|
+
activerecord (= 6.1.3.1)
|
|
39
|
+
activestorage (= 6.1.3.1)
|
|
40
|
+
activesupport (= 6.1.3.1)
|
|
41
|
+
nokogiri (>= 1.8.5)
|
|
42
|
+
actionview (6.1.3.1)
|
|
43
|
+
activesupport (= 6.1.3.1)
|
|
44
|
+
builder (~> 3.1)
|
|
45
|
+
erubi (~> 1.4)
|
|
46
|
+
rails-dom-testing (~> 2.0)
|
|
47
|
+
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
|
48
|
+
activejob (6.1.3.1)
|
|
49
|
+
activesupport (= 6.1.3.1)
|
|
50
|
+
globalid (>= 0.3.6)
|
|
51
|
+
activemodel (6.1.3.1)
|
|
52
|
+
activesupport (= 6.1.3.1)
|
|
53
|
+
activerecord (6.1.3.1)
|
|
54
|
+
activemodel (= 6.1.3.1)
|
|
55
|
+
activesupport (= 6.1.3.1)
|
|
56
|
+
activestorage (6.1.3.1)
|
|
57
|
+
actionpack (= 6.1.3.1)
|
|
58
|
+
activejob (= 6.1.3.1)
|
|
59
|
+
activerecord (= 6.1.3.1)
|
|
60
|
+
activesupport (= 6.1.3.1)
|
|
61
|
+
marcel (~> 1.0.0)
|
|
62
|
+
mini_mime (~> 1.0.2)
|
|
63
|
+
activesupport (6.1.3.1)
|
|
11
64
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
12
65
|
i18n (>= 1.6, < 2)
|
|
13
66
|
minitest (>= 5.1)
|
|
14
67
|
tzinfo (~> 2.0)
|
|
15
68
|
zeitwerk (~> 2.3)
|
|
69
|
+
builder (3.2.4)
|
|
70
|
+
byebug (11.1.3)
|
|
16
71
|
concurrent-ruby (1.1.8)
|
|
17
|
-
|
|
18
|
-
|
|
72
|
+
crass (1.0.6)
|
|
73
|
+
diff-lcs (1.4.4)
|
|
74
|
+
erubi (1.10.0)
|
|
75
|
+
globalid (0.4.2)
|
|
76
|
+
activesupport (>= 4.2.0)
|
|
77
|
+
i18n (1.8.10)
|
|
19
78
|
concurrent-ruby (~> 1.0)
|
|
79
|
+
loofah (2.9.0)
|
|
80
|
+
crass (~> 1.0.2)
|
|
81
|
+
nokogiri (>= 1.5.9)
|
|
82
|
+
mail (2.7.1)
|
|
83
|
+
mini_mime (>= 0.1.1)
|
|
84
|
+
marcel (1.0.1)
|
|
85
|
+
method_source (1.0.0)
|
|
86
|
+
mini_mime (1.0.3)
|
|
87
|
+
mini_portile2 (2.5.0)
|
|
20
88
|
minitest (5.14.4)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
89
|
+
nio4r (2.5.7)
|
|
90
|
+
nokogiri (1.11.2)
|
|
91
|
+
mini_portile2 (~> 2.5.0)
|
|
92
|
+
racc (~> 1.4)
|
|
93
|
+
racc (1.5.2)
|
|
94
|
+
rack (2.2.3)
|
|
95
|
+
rack-test (1.1.0)
|
|
96
|
+
rack (>= 1.0, < 3)
|
|
97
|
+
rails (6.1.3.1)
|
|
98
|
+
actioncable (= 6.1.3.1)
|
|
99
|
+
actionmailbox (= 6.1.3.1)
|
|
100
|
+
actionmailer (= 6.1.3.1)
|
|
101
|
+
actionpack (= 6.1.3.1)
|
|
102
|
+
actiontext (= 6.1.3.1)
|
|
103
|
+
actionview (= 6.1.3.1)
|
|
104
|
+
activejob (= 6.1.3.1)
|
|
105
|
+
activemodel (= 6.1.3.1)
|
|
106
|
+
activerecord (= 6.1.3.1)
|
|
107
|
+
activestorage (= 6.1.3.1)
|
|
108
|
+
activesupport (= 6.1.3.1)
|
|
109
|
+
bundler (>= 1.15.0)
|
|
110
|
+
railties (= 6.1.3.1)
|
|
111
|
+
sprockets-rails (>= 2.0.0)
|
|
112
|
+
rails-dom-testing (2.0.3)
|
|
113
|
+
activesupport (>= 4.2.0)
|
|
114
|
+
nokogiri (>= 1.6)
|
|
115
|
+
rails-html-sanitizer (1.3.0)
|
|
116
|
+
loofah (~> 2.3)
|
|
117
|
+
railties (6.1.3.1)
|
|
118
|
+
actionpack (= 6.1.3.1)
|
|
119
|
+
activesupport (= 6.1.3.1)
|
|
120
|
+
method_source
|
|
121
|
+
rake (>= 0.8.7)
|
|
122
|
+
thor (~> 1.0)
|
|
123
|
+
rake (13.0.3)
|
|
124
|
+
rspec (3.10.0)
|
|
125
|
+
rspec-core (~> 3.10.0)
|
|
126
|
+
rspec-expectations (~> 3.10.0)
|
|
127
|
+
rspec-mocks (~> 3.10.0)
|
|
128
|
+
rspec-core (3.10.1)
|
|
129
|
+
rspec-support (~> 3.10.0)
|
|
130
|
+
rspec-expectations (3.10.1)
|
|
29
131
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
30
|
-
rspec-support (~> 3.
|
|
31
|
-
rspec-mocks (3.
|
|
132
|
+
rspec-support (~> 3.10.0)
|
|
133
|
+
rspec-mocks (3.10.2)
|
|
32
134
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
33
|
-
rspec-support (~> 3.
|
|
34
|
-
rspec-
|
|
135
|
+
rspec-support (~> 3.10.0)
|
|
136
|
+
rspec-rails (5.0.1)
|
|
137
|
+
actionpack (>= 5.2)
|
|
138
|
+
activesupport (>= 5.2)
|
|
139
|
+
railties (>= 5.2)
|
|
140
|
+
rspec-core (~> 3.10)
|
|
141
|
+
rspec-expectations (~> 3.10)
|
|
142
|
+
rspec-mocks (~> 3.10)
|
|
143
|
+
rspec-support (~> 3.10)
|
|
144
|
+
rspec-support (3.10.2)
|
|
145
|
+
sprockets (4.0.2)
|
|
146
|
+
concurrent-ruby (~> 1.0)
|
|
147
|
+
rack (> 1, < 3)
|
|
148
|
+
sprockets-rails (3.2.2)
|
|
149
|
+
actionpack (>= 4.0)
|
|
150
|
+
activesupport (>= 4.0)
|
|
151
|
+
sprockets (>= 3.0.0)
|
|
152
|
+
sqlite3 (1.4.2)
|
|
153
|
+
thor (1.1.0)
|
|
35
154
|
tzinfo (2.0.4)
|
|
36
155
|
concurrent-ruby (~> 1.0)
|
|
156
|
+
websocket-driver (0.7.3)
|
|
157
|
+
websocket-extensions (>= 0.1.0)
|
|
158
|
+
websocket-extensions (0.1.5)
|
|
37
159
|
zeitwerk (2.4.2)
|
|
38
160
|
|
|
39
161
|
PLATFORMS
|
|
@@ -41,9 +163,13 @@ PLATFORMS
|
|
|
41
163
|
|
|
42
164
|
DEPENDENCIES
|
|
43
165
|
bundler (~> 1.16)
|
|
166
|
+
byebug
|
|
44
167
|
json_api_filter!
|
|
168
|
+
rails
|
|
45
169
|
rake (~> 13.0)
|
|
46
|
-
rspec (
|
|
170
|
+
rspec (>= 3.0)
|
|
171
|
+
rspec-rails
|
|
172
|
+
sqlite3
|
|
47
173
|
|
|
48
174
|
BUNDLED WITH
|
|
49
175
|
1.17.2
|
data/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# JsonApiFilter
|
|
2
2
|
|
|
3
|
-
Filter for rails controller based on JsonAPI spec: `/books?filter[library_id]=1,2&filter[author_id]=12`
|
|
3
|
+
Filter for rails controller based on JsonAPI spec: `/books?filter[library_id]=1,2&filter[author_id][eq]=12&filter[created_at][gt]=2021-02-02`
|
|
4
4
|
|
|
5
|
+
[](https://codeclimate.com/github/Blaked84/json_api_filter/maintainability)
|
|
5
6
|
[](https://badge.fury.io/rb/json_api_filter)
|
|
7
|
+
[](https://travis-ci.com/Blaked84/json_api_filter)
|
|
6
8
|
|
|
7
9
|
## Installation
|
|
8
10
|
|
|
@@ -13,27 +15,30 @@ gem 'json_api_filter'
|
|
|
13
15
|
```
|
|
14
16
|
|
|
15
17
|
And then execute:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
```bash
|
|
19
|
+
$ bundle
|
|
20
|
+
```
|
|
18
21
|
|
|
19
22
|
Or install it yourself as:
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
```bash
|
|
24
|
+
$ gem install json_api_filter
|
|
25
|
+
```
|
|
22
26
|
|
|
23
27
|
## Usage
|
|
24
28
|
|
|
25
29
|
### Quick start
|
|
26
30
|
|
|
27
|
-
To filter this request `/books?filter[library_id]=1,2&filter[author_id]=12`
|
|
31
|
+
To filter this request `/books?filter[library_id]=1,2&filter[author_id]=12&search=Lord of the ring`
|
|
28
32
|
|
|
29
33
|
```ruby
|
|
30
34
|
class Book < ApplicationController
|
|
31
35
|
|
|
32
36
|
include JsonApiFilter
|
|
33
37
|
permitted_filters %i[library_id author_id]
|
|
38
|
+
permitted_searches :user_search
|
|
34
39
|
|
|
35
40
|
def index
|
|
36
|
-
@books = Book
|
|
41
|
+
@books = json_api_filter(Book, params)
|
|
37
42
|
end
|
|
38
43
|
|
|
39
44
|
end
|
|
@@ -41,7 +46,27 @@ end
|
|
|
41
46
|
```
|
|
42
47
|
|
|
43
48
|
- `permitted_filters` let you define allowed attributes to filter on (mandatory)
|
|
44
|
-
- `
|
|
49
|
+
- `permitted_searches` let you define the allowed search method defined in you model what will be called if you pass `search` params in your request (can be a pg_search scope)
|
|
50
|
+
- `json_api_filter(scope, params)` return an active record relation (`Book::` in this example)
|
|
51
|
+
|
|
52
|
+
## Migration from 0.1
|
|
53
|
+
0.2.x version is not compatible with 0.1
|
|
54
|
+
In your controller, you will have to replace all occurrences of `attr_filter` as bellow :
|
|
55
|
+
|
|
56
|
+
### Before
|
|
57
|
+
```ruby
|
|
58
|
+
def index
|
|
59
|
+
@books = Book.all.where(attr_filter(params))
|
|
60
|
+
end
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### After
|
|
64
|
+
```ruby
|
|
65
|
+
def index
|
|
66
|
+
@books = json_api_filter(Book, params)
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
45
70
|
## Development
|
|
46
71
|
|
|
47
72
|
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/json_api_filter.gemspec
CHANGED
|
@@ -37,6 +37,10 @@ Gem::Specification.new do |spec|
|
|
|
37
37
|
|
|
38
38
|
spec.add_development_dependency "bundler", "~> 1.16"
|
|
39
39
|
spec.add_development_dependency "rake", "~> 13.0"
|
|
40
|
-
spec.add_development_dependency "rspec", "
|
|
40
|
+
spec.add_development_dependency "rspec", ">= 3.0"
|
|
41
|
+
spec.add_development_dependency "rspec-rails"
|
|
42
|
+
spec.add_development_dependency "sqlite3"
|
|
43
|
+
spec.add_development_dependency 'rails'
|
|
44
|
+
spec.add_development_dependency 'byebug'
|
|
41
45
|
spec.add_dependency "activesupport", ">= 3.0.0"
|
|
42
46
|
end
|
data/lib/json_api_filter.rb
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
require "json_api_filter/version"
|
|
2
|
-
require "json_api_filter/
|
|
3
|
-
require "json_api_filter/
|
|
2
|
+
require "json_api_filter/auto_join"
|
|
3
|
+
require "json_api_filter/dispatch"
|
|
4
|
+
require "json_api_filter/filter_attributes"
|
|
5
|
+
require "json_api_filter/value_parser"
|
|
6
|
+
require "json_api_filter/field_filters/base"
|
|
7
|
+
require "json_api_filter/field_filters/matcher"
|
|
8
|
+
require "json_api_filter/field_filters/compare"
|
|
9
|
+
require "json_api_filter/field_filters/searcher"
|
|
10
|
+
require "json_api_filter/field_filters/sorter"
|
|
11
|
+
require "json_api_filter/field_filters/pagination"
|
|
4
12
|
require "active_support/concern"
|
|
5
13
|
require "active_support/core_ext/object/blank"
|
|
6
14
|
|
|
@@ -13,16 +21,19 @@ module JsonApiFilter
|
|
|
13
21
|
|
|
14
22
|
extend ::ActiveSupport::Concern
|
|
15
23
|
included do
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
|
|
25
|
+
# @param [ActiveRecord::Base] scope
|
|
26
|
+
def json_api_filter(scope, query_params = params)
|
|
18
27
|
unless self.class.json_api_permitted_filters.present?
|
|
19
28
|
raise ::JsonApiFilter::MissingPermittedFilterError
|
|
20
29
|
end
|
|
21
30
|
|
|
22
|
-
::JsonApiFilter::
|
|
23
|
-
|
|
24
|
-
query_params
|
|
25
|
-
|
|
31
|
+
::JsonApiFilter::Dispatch.new(
|
|
32
|
+
scope,
|
|
33
|
+
query_params,
|
|
34
|
+
allowed_filters: self.class.json_api_permitted_filters,
|
|
35
|
+
allowed_searches: self.class.json_api_permitted_searches
|
|
36
|
+
).process
|
|
26
37
|
end
|
|
27
38
|
|
|
28
39
|
def self.permitted_filters(val)
|
|
@@ -34,5 +45,15 @@ module JsonApiFilter
|
|
|
34
45
|
def self.json_api_permitted_filters
|
|
35
46
|
[]
|
|
36
47
|
end
|
|
48
|
+
|
|
49
|
+
def self.permitted_searches(global, **columns)
|
|
50
|
+
define_singleton_method(:json_api_permitted_searches) do
|
|
51
|
+
{ global: global, columns: columns }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.json_api_permitted_searches
|
|
56
|
+
{}
|
|
57
|
+
end
|
|
37
58
|
end
|
|
38
59
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module JsonApiFilter
|
|
2
|
+
class AutoJoin
|
|
3
|
+
|
|
4
|
+
attr_reader :association_name, :scope
|
|
5
|
+
|
|
6
|
+
# @param [ActiveRecord::Base, ActiveRecord_Relation] scope
|
|
7
|
+
# @param [String, Symbol] association_name
|
|
8
|
+
def initialize(scope, association_name)
|
|
9
|
+
@association_name = association_name
|
|
10
|
+
@scope = scope
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def predicate
|
|
14
|
+
scope.joins(association_name.to_sym)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
module JsonApiFilter
|
|
2
|
+
class Dispatch
|
|
3
|
+
|
|
4
|
+
attr_reader :params, :scope, :allowed_filters, :allowed_searches
|
|
5
|
+
|
|
6
|
+
# @param [ActiveRecord::Base] scope
|
|
7
|
+
# @param [Hash, ActionController::Parameters] params
|
|
8
|
+
# @param [Array<Symbol>] allowed_filters
|
|
9
|
+
def initialize(scope, params, allowed_filters:, allowed_searches:)
|
|
10
|
+
@params = params
|
|
11
|
+
@scope = scope
|
|
12
|
+
@allowed_filters = allowed_filters
|
|
13
|
+
@allowed_searches = allowed_searches
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @return [ActiveRecord_Relation]
|
|
17
|
+
def process
|
|
18
|
+
@scope = [
|
|
19
|
+
scope.all,
|
|
20
|
+
sort_predicate,
|
|
21
|
+
filters_predicate,
|
|
22
|
+
search_predicate,
|
|
23
|
+
join_predicate,
|
|
24
|
+
].compact.reduce(&:merge)
|
|
25
|
+
return scope if params[:pagination].nil?
|
|
26
|
+
scope.merge(pagination_predicate)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
# @return [ActiveRecord::Base, NilClass]
|
|
32
|
+
def filters_predicate
|
|
33
|
+
#TODO: split this method
|
|
34
|
+
parser_params.fetch('filter', {}).map do |key, value|
|
|
35
|
+
next unless filters.include?(key)
|
|
36
|
+
|
|
37
|
+
if nested_filters.include?(key.to_sym)
|
|
38
|
+
next ::JsonApiFilter::FieldFilters::Matcher.new(
|
|
39
|
+
scope,
|
|
40
|
+
{key => value},
|
|
41
|
+
association: true
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if value.class != ActiveSupport::HashWithIndifferentAccess
|
|
46
|
+
next ::JsonApiFilter::FieldFilters::Matcher.new(scope, {key => value})
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
::JsonApiFilter::FieldFilters::Compare.new(
|
|
50
|
+
scope,
|
|
51
|
+
{key => value},
|
|
52
|
+
allowed_searches: allowed_searches
|
|
53
|
+
)
|
|
54
|
+
end.compact.map(&:predicate).reduce(&:merge)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [ActiveRecord::Base, NilClass]
|
|
58
|
+
def sort_predicate
|
|
59
|
+
sort = parser_params[:sort]
|
|
60
|
+
return nil if sort.blank?
|
|
61
|
+
|
|
62
|
+
::JsonApiFilter::FieldFilters::Sorter.new(scope, sort).predicate
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [ActiveRecord::Base, NilClass]
|
|
66
|
+
def search_predicate
|
|
67
|
+
return nil if parser_params[:search].blank?
|
|
68
|
+
return nil if allowed_searches[:global].nil?
|
|
69
|
+
|
|
70
|
+
::JsonApiFilter::FieldFilters::Searcher.new(
|
|
71
|
+
scope,
|
|
72
|
+
{allowed_searches[:global] => parser_params[:search]}
|
|
73
|
+
).predicate
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @return [ActiveRecord::Base, NilClass]
|
|
77
|
+
def pagination_predicate
|
|
78
|
+
return nil if parser_params[:pagination].nil?
|
|
79
|
+
|
|
80
|
+
::JsonApiFilter::FieldFilters::Pagination.new(
|
|
81
|
+
scope,
|
|
82
|
+
parser_params[:pagination]
|
|
83
|
+
).predicate
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# @return [ActiveRecord::Base, NilClass]
|
|
87
|
+
def join_predicate
|
|
88
|
+
parser_params.fetch('filter', {}).map do |key, value|
|
|
89
|
+
next unless nested_filters.include?(key.to_sym)
|
|
90
|
+
|
|
91
|
+
::JsonApiFilter::AutoJoin.new(scope, key)
|
|
92
|
+
end.compact.map(&:predicate).reduce(&:merge)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @return [Hash]
|
|
96
|
+
def parser_params
|
|
97
|
+
return params.to_unsafe_h if params.class == ActionController::Parameters
|
|
98
|
+
|
|
99
|
+
params
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @return [Hash]
|
|
103
|
+
def filters
|
|
104
|
+
FilterAttributes.new(allowed_filters, parser_params[:filter]).process
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def nested_filters
|
|
108
|
+
FilterAttributes.new(allowed_filters, parser_params[:filter]).nested_allowed_filter
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module JsonApiFilter
|
|
2
|
+
module FieldFilters
|
|
3
|
+
class Base
|
|
4
|
+
|
|
5
|
+
attr_reader :scope, :values, :association
|
|
6
|
+
|
|
7
|
+
# @param [ActiveRecord::Base] scope
|
|
8
|
+
# @param [Hash] values
|
|
9
|
+
# @param [Boolean] association
|
|
10
|
+
def initialize(scope, values, association: false )
|
|
11
|
+
@scope = scope
|
|
12
|
+
@values = values
|
|
13
|
+
@association = association
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
|
|
2
|
+
module JsonApiFilter
|
|
3
|
+
module FieldFilters
|
|
4
|
+
class Compare < Base
|
|
5
|
+
|
|
6
|
+
attr_reader :allowed_searches
|
|
7
|
+
|
|
8
|
+
def initialize(scope, values, allowed_searches:)
|
|
9
|
+
super(scope, values)
|
|
10
|
+
@allowed_searches = allowed_searches
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @return [ActiveRecord_Relation]
|
|
14
|
+
def predicate
|
|
15
|
+
column = values.keys.first
|
|
16
|
+
filter = values.first[1]
|
|
17
|
+
filter.map do |key, value|
|
|
18
|
+
if !WHERE_METHODS[key.to_sym].nil?
|
|
19
|
+
compare(column, key, value)
|
|
20
|
+
elsif key == "search"
|
|
21
|
+
next unless allowed_searches[:columns].keys.include?(column.to_sym)
|
|
22
|
+
::JsonApiFilter::FieldFilters::Searcher.new(
|
|
23
|
+
scope,
|
|
24
|
+
{allowed_searches[:columns][column.to_sym] => value}
|
|
25
|
+
).predicate
|
|
26
|
+
else
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
end.compact.reduce(&:merge)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
WHERE_METHODS = {
|
|
35
|
+
eq: "=",
|
|
36
|
+
ne: "!=",
|
|
37
|
+
gt: ">",
|
|
38
|
+
ge: ">=",
|
|
39
|
+
lt: "<",
|
|
40
|
+
le: "<=",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
def compare(column, method, value)
|
|
44
|
+
if WHERE_METHODS[method.to_sym] == "="
|
|
45
|
+
# prefer this method for eq as it implicitely transforms enum into their integer representation
|
|
46
|
+
scope.where(column => value)
|
|
47
|
+
else
|
|
48
|
+
scope.where("#{column} #{WHERE_METHODS[method.to_sym]} ?", value)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module JsonApiFilter
|
|
2
|
+
module FieldFilters
|
|
3
|
+
class Matcher < Base
|
|
4
|
+
|
|
5
|
+
# @return [ActiveRecord_Relation]
|
|
6
|
+
def predicate
|
|
7
|
+
|
|
8
|
+
values.map do |key, value|
|
|
9
|
+
scope.where(
|
|
10
|
+
key_translation(key) => ::JsonApiFilter::ValueParser.parse(value)
|
|
11
|
+
)
|
|
12
|
+
end.reduce(&:merge)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
#TODO: move to another class
|
|
18
|
+
def key_translation(key)
|
|
19
|
+
return key unless association
|
|
20
|
+
|
|
21
|
+
table_name(key)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @return [ActiveRecord::Base]
|
|
25
|
+
def model_klass
|
|
26
|
+
scope.try(:klass) || scope
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def table_name(association_name)
|
|
30
|
+
model_klass.reflections[association_name.to_s].table_name
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module JsonApiFilter
|
|
2
|
+
module FieldFilters
|
|
3
|
+
class Pagination < Base
|
|
4
|
+
|
|
5
|
+
# @return [ActiveRecord_Relation]
|
|
6
|
+
def predicate
|
|
7
|
+
page = values["page"]
|
|
8
|
+
per_page = values["perPage"]
|
|
9
|
+
result = scope
|
|
10
|
+
unless page.nil? || per_page.nil? || per_page == "-1"
|
|
11
|
+
result = result.limit(per_page.to_i)
|
|
12
|
+
result = result.offset((page.to_i - 1) * per_page.to_i)
|
|
13
|
+
end
|
|
14
|
+
result
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module JsonApiFilter
|
|
2
|
+
module FieldFilters
|
|
3
|
+
class Sorter < Base
|
|
4
|
+
|
|
5
|
+
# @return [ActiveRecord_Relation]
|
|
6
|
+
def predicate
|
|
7
|
+
return nil if values["by"].nil?
|
|
8
|
+
scope.order(values["by"] => order)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def order
|
|
14
|
+
ActiveModel::Type::Boolean.new.cast(values["desc"]) ? :desc : :asc
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module JsonApiFilter
|
|
2
|
+
class FilterAttributes
|
|
3
|
+
attr_reader :allowed_filters, :params
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# @param [ActiveSupport::HashWithIndifferentAccess, Hash] allowed_filters
|
|
7
|
+
# @param [ActiveSupport::HashWithIndifferentAccess, Hash] params
|
|
8
|
+
def initialize(allowed_filters, params)
|
|
9
|
+
@allowed_filters = allowed_filters
|
|
10
|
+
@params = params
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @return [ActiveSupport::HashWithIndifferentAccess, Hash]
|
|
14
|
+
def process
|
|
15
|
+
params.select do |k,v|
|
|
16
|
+
allowed_filters.include?(k.to_sym) ||
|
|
17
|
+
allowed_filters.include?(k.to_s) ||
|
|
18
|
+
nested_allowed_filter.include?(k.to_sym) ||
|
|
19
|
+
nested_allowed_filter.include?(k.to_s)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Keys of nested allowed filters
|
|
24
|
+
# only one level of nesting supported
|
|
25
|
+
#
|
|
26
|
+
# @example
|
|
27
|
+
# for: permitted_filters [:id, :author, :name, users: [:id]]
|
|
28
|
+
# it returns: :users
|
|
29
|
+
#
|
|
30
|
+
# @return [Array<Symbol>]
|
|
31
|
+
def nested_allowed_filter
|
|
32
|
+
allowed_filters.select{|f| f.class == Hash}.map(&:keys).flatten
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module JsonApiFilter
|
|
2
|
+
class ValueParser
|
|
3
|
+
|
|
4
|
+
attr_reader :value
|
|
5
|
+
|
|
6
|
+
# @param [String, Hash, HashWithIndifferentAccess] value
|
|
7
|
+
def initialize(value)
|
|
8
|
+
@value = value
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Transform string of coma separated values to Array
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# "1,2,3" #=> [1,2,3]
|
|
15
|
+
#
|
|
16
|
+
# @param [String, Hash, HashWithIndifferentAccess] value
|
|
17
|
+
# @return [Array]
|
|
18
|
+
def self.parse(value)
|
|
19
|
+
new(value).indifferent_parse
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def indifferent_parse
|
|
23
|
+
return parse if value.is_a? String
|
|
24
|
+
parse_hash if value.is_a? HashWithIndifferentAccess
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def parse
|
|
28
|
+
return [value] unless value.include?(",")
|
|
29
|
+
value.split(',')
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def parse_hash
|
|
33
|
+
value.map{|k,v| {k => self.class.parse(v)}}.reduce(&:merge)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
37
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: json_api_filter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: '0.2'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Blaked
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2021-03
|
|
11
|
+
date: 2021-05-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -42,16 +42,72 @@ dependencies:
|
|
|
42
42
|
name: rspec
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
|
-
- - "
|
|
45
|
+
- - ">="
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
47
|
version: '3.0'
|
|
48
48
|
type: :development
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
|
-
- - "
|
|
52
|
+
- - ">="
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '3.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec-rails
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: sqlite3
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rails
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - ">="
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: byebug
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '0'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - ">="
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '0'
|
|
55
111
|
- !ruby/object:Gem::Dependency
|
|
56
112
|
name: activesupport
|
|
57
113
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -86,8 +142,16 @@ files:
|
|
|
86
142
|
- bin/setup
|
|
87
143
|
- json_api_filter.gemspec
|
|
88
144
|
- lib/json_api_filter.rb
|
|
89
|
-
- lib/json_api_filter/
|
|
90
|
-
- lib/json_api_filter/
|
|
145
|
+
- lib/json_api_filter/auto_join.rb
|
|
146
|
+
- lib/json_api_filter/dispatch.rb
|
|
147
|
+
- lib/json_api_filter/field_filters/base.rb
|
|
148
|
+
- lib/json_api_filter/field_filters/compare.rb
|
|
149
|
+
- lib/json_api_filter/field_filters/matcher.rb
|
|
150
|
+
- lib/json_api_filter/field_filters/pagination.rb
|
|
151
|
+
- lib/json_api_filter/field_filters/searcher.rb
|
|
152
|
+
- lib/json_api_filter/field_filters/sorter.rb
|
|
153
|
+
- lib/json_api_filter/filter_attributes.rb
|
|
154
|
+
- lib/json_api_filter/value_parser.rb
|
|
91
155
|
- lib/json_api_filter/version.rb
|
|
92
156
|
homepage: https://github.com/Blaked84/json_api_filter
|
|
93
157
|
licenses:
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
module JsonApiFilter
|
|
2
|
-
class FilterBy
|
|
3
|
-
|
|
4
|
-
attr_reader :attr, :value
|
|
5
|
-
|
|
6
|
-
def initialize(attr, value)
|
|
7
|
-
@attr = attr
|
|
8
|
-
@value = value
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def to_hash
|
|
12
|
-
return {} unless parsed_value.present?
|
|
13
|
-
|
|
14
|
-
{ key.to_sym => parsed_value }
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
private
|
|
18
|
-
|
|
19
|
-
def key
|
|
20
|
-
attr.to_sym
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def parsed_value
|
|
24
|
-
value.split(',')
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
module JsonApiFilter
|
|
2
|
-
class Filters
|
|
3
|
-
attr_reader :allowed_filters, :params
|
|
4
|
-
|
|
5
|
-
def initialize(allowed_filters, params)
|
|
6
|
-
@allowed_filters = allowed_filters
|
|
7
|
-
@params = params
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def to_hash
|
|
11
|
-
filters.inject(:merge)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
private
|
|
15
|
-
|
|
16
|
-
def filters
|
|
17
|
-
allowed_filters.map do |attr|
|
|
18
|
-
value = params.fetch(:filter, {}).fetch(attr, "")
|
|
19
|
-
::JsonApiFilter::FilterBy.new(attr, value).to_hash
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|