json_api_filter 0.2 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a0e5c959706685cf6cc047d4824c7ed0e0c35778b6b952aa3b305e5e0eb40f13
4
- data.tar.gz: 3a87503e2341ae745d0ec6b5154049c95b0a28b527aeb8903ed000e53685bf3b
3
+ metadata.gz: 26dba474ac325ef0b01c1f2a6046d94ec4883e497eb892fc5646744eb0e67273
4
+ data.tar.gz: 627dffb840e95036c4617175345702278c6221f7fc513033b94d8eb0b343e0cc
5
5
  SHA512:
6
- metadata.gz: fee62197e5d3814185e35f7935b76daba4c96dcabd2e655e250512f3267e8295512fffe16301df3b18b089c4eb5b5cbed1a120793dd60518929aa666ba16b5e2
7
- data.tar.gz: c183c0a031bac479dfa66baf3cb90a9ef6735719c7dc61a12fcbda50412a0239e5dcd6ca4ebd3ddffae23588f9cd90793123a79cd2a44107723a9f869b5d4b2f
6
+ metadata.gz: b6afe3cc8426481a78cf30287ccd153c5f55c981b593a533e3f1941d376b5e72453dd4537a478b73b5600154559d57529390d66cadc87380be78036897b4ef22
7
+ data.tar.gz: 607c4107b32a551de880edccd2f10c86711fa45250120756bd390eff953ed44677e9c28a69ecc19ec5b0ed832a76f4d024684198897ef9847d6c710ecb6d7751
@@ -0,0 +1,44 @@
1
+ name: Ruby Gem
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [ master ]
7
+
8
+ jobs:
9
+ build:
10
+ name: Build + Publish
11
+ runs-on: ubuntu-latest
12
+ permissions:
13
+ contents: read
14
+ packages: write
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - name: Set up Ruby
19
+ uses: ruby/setup-ruby@v1
20
+ with:
21
+ ruby-version: 3.3.4
22
+ bundler: 2.5.11
23
+ bundler-cache: true
24
+
25
+ - name: Set up the test database
26
+ run: |
27
+ bundle install
28
+ bundle exec rails db:setup
29
+ working-directory: spec/dummy
30
+
31
+ - name: Run tests
32
+ run: bundle exec rspec
33
+
34
+ - name: Publish to RubyGems
35
+ if: github.event_name == 'push'
36
+ run: |
37
+ mkdir -p $HOME/.gem
38
+ touch $HOME/.gem/credentials
39
+ chmod 0600 $HOME/.gem/credentials
40
+ printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
41
+ gem build *.gemspec
42
+ gem push *.gem
43
+ env:
44
+ GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.3.4
data/.travis.yml CHANGED
@@ -3,8 +3,9 @@ sudo: false
3
3
  language: ruby
4
4
  cache: bundler
5
5
  rvm:
6
- - 2.6.3
7
- before_install: gem install bundler -v 1.17.2
6
+ - 3.3.4
7
+ before_install: gem install bundler -v 2.5.11
8
8
  script:
9
+ - (cd spec/dummy && bundle install)
9
10
  - (cd spec/dummy && RAILS_ENV=test bundle exec rails db:setup)
10
11
  - bundle exec rspec
data/Gemfile.lock CHANGED
@@ -1,175 +1,249 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- json_api_filter (0.2)
4
+ json_api_filter (0.4.0)
5
5
  activesupport (>= 3.0.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- actioncable (6.1.3.1)
11
- actionpack (= 6.1.3.1)
12
- activesupport (= 6.1.3.1)
10
+ actioncable (7.1.6)
11
+ actionpack (= 7.1.6)
12
+ activesupport (= 7.1.6)
13
13
  nio4r (~> 2.0)
14
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)
15
+ zeitwerk (~> 2.6)
16
+ actionmailbox (7.1.6)
17
+ actionpack (= 7.1.6)
18
+ activejob (= 7.1.6)
19
+ activerecord (= 7.1.6)
20
+ activestorage (= 7.1.6)
21
+ activesupport (= 7.1.6)
21
22
  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)
23
+ net-imap
24
+ net-pop
25
+ net-smtp
26
+ actionmailer (7.1.6)
27
+ actionpack (= 7.1.6)
28
+ actionview (= 7.1.6)
29
+ activejob (= 7.1.6)
30
+ activesupport (= 7.1.6)
27
31
  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)
32
+ net-imap
33
+ net-pop
34
+ net-smtp
35
+ rails-dom-testing (~> 2.2)
36
+ actionpack (7.1.6)
37
+ actionview (= 7.1.6)
38
+ activesupport (= 7.1.6)
39
+ cgi
40
+ nokogiri (>= 1.8.5)
41
+ racc
42
+ rack (>= 2.2.4)
43
+ rack-session (>= 1.0.1)
33
44
  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)
45
+ rails-dom-testing (~> 2.2)
46
+ rails-html-sanitizer (~> 1.6)
47
+ actiontext (7.1.6)
48
+ actionpack (= 7.1.6)
49
+ activerecord (= 7.1.6)
50
+ activestorage (= 7.1.6)
51
+ activesupport (= 7.1.6)
52
+ globalid (>= 0.6.0)
41
53
  nokogiri (>= 1.8.5)
42
- actionview (6.1.3.1)
43
- activesupport (= 6.1.3.1)
54
+ actionview (7.1.6)
55
+ activesupport (= 7.1.6)
44
56
  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)
57
+ cgi
58
+ erubi (~> 1.11)
59
+ rails-dom-testing (~> 2.2)
60
+ rails-html-sanitizer (~> 1.6)
61
+ activejob (7.1.6)
62
+ activesupport (= 7.1.6)
50
63
  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)
64
+ activemodel (7.1.6)
65
+ activesupport (= 7.1.6)
66
+ activerecord (7.1.6)
67
+ activemodel (= 7.1.6)
68
+ activesupport (= 7.1.6)
69
+ timeout (>= 0.4.0)
70
+ activestorage (7.1.6)
71
+ actionpack (= 7.1.6)
72
+ activejob (= 7.1.6)
73
+ activerecord (= 7.1.6)
74
+ activesupport (= 7.1.6)
75
+ marcel (~> 1.0)
76
+ activesupport (7.1.6)
77
+ base64
78
+ benchmark (>= 0.3)
79
+ bigdecimal
64
80
  concurrent-ruby (~> 1.0, >= 1.0.2)
81
+ connection_pool (>= 2.2.5)
82
+ drb
65
83
  i18n (>= 1.6, < 2)
84
+ logger (>= 1.4.2)
66
85
  minitest (>= 5.1)
86
+ mutex_m
87
+ securerandom (>= 0.3)
67
88
  tzinfo (~> 2.0)
68
- zeitwerk (~> 2.3)
69
- builder (3.2.4)
70
- byebug (11.1.3)
71
- concurrent-ruby (1.1.8)
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)
89
+ base64 (0.3.0)
90
+ benchmark (0.5.0)
91
+ bigdecimal (4.1.2)
92
+ builder (3.3.0)
93
+ byebug (13.0.0)
94
+ reline (>= 0.6.0)
95
+ cgi (0.5.2)
96
+ concurrent-ruby (1.3.8)
97
+ connection_pool (3.0.2)
98
+ crass (1.0.7)
99
+ date (3.5.1)
100
+ diff-lcs (1.6.2)
101
+ drb (2.2.3)
102
+ erb (6.0.7)
103
+ erubi (1.13.1)
104
+ globalid (1.4.0)
105
+ activesupport (>= 6.1)
106
+ i18n (1.15.2)
78
107
  concurrent-ruby (~> 1.0)
79
- loofah (2.9.0)
108
+ io-console (0.9.2)
109
+ irb (1.18.0)
110
+ pp (>= 0.6.0)
111
+ prism (>= 1.3.0)
112
+ rdoc (>= 4.0.0)
113
+ reline (>= 0.4.2)
114
+ logger (1.7.0)
115
+ loofah (2.25.2)
80
116
  crass (~> 1.0.2)
81
- nokogiri (>= 1.5.9)
82
- mail (2.7.1)
117
+ nokogiri (>= 1.12.0)
118
+ mail (2.9.1)
119
+ logger
83
120
  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)
88
- minitest (5.14.4)
89
- nio4r (2.5.7)
90
- nokogiri (1.11.2)
91
- mini_portile2 (~> 2.5.0)
121
+ net-imap
122
+ net-pop
123
+ net-smtp
124
+ marcel (1.2.1)
125
+ mini_mime (1.1.5)
126
+ mini_portile2 (2.8.9)
127
+ minitest (6.0.6)
128
+ drb (~> 2.0)
129
+ prism (~> 1.5)
130
+ mutex_m (0.3.0)
131
+ net-imap (0.6.6)
132
+ date
133
+ net-protocol
134
+ net-pop (0.1.2)
135
+ net-protocol
136
+ net-protocol (0.3.0)
137
+ timeout
138
+ net-smtp (0.5.1)
139
+ net-protocol
140
+ nio4r (2.7.5)
141
+ nokogiri (1.19.4)
142
+ mini_portile2 (~> 2.8.2)
92
143
  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)
144
+ pp (0.6.4)
145
+ prettyprint
146
+ prettyprint (0.2.0)
147
+ prism (1.9.0)
148
+ racc (1.8.1)
149
+ rack (3.2.7)
150
+ rack-session (2.1.2)
151
+ base64 (>= 0.1.0)
152
+ rack (>= 3.0.0)
153
+ rack-test (2.2.0)
154
+ rack (>= 1.3)
155
+ rackup (2.3.1)
156
+ rack (>= 3)
157
+ rails (7.1.6)
158
+ actioncable (= 7.1.6)
159
+ actionmailbox (= 7.1.6)
160
+ actionmailer (= 7.1.6)
161
+ actionpack (= 7.1.6)
162
+ actiontext (= 7.1.6)
163
+ actionview (= 7.1.6)
164
+ activejob (= 7.1.6)
165
+ activemodel (= 7.1.6)
166
+ activerecord (= 7.1.6)
167
+ activestorage (= 7.1.6)
168
+ activesupport (= 7.1.6)
109
169
  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)
170
+ railties (= 7.1.6)
171
+ rails-dom-testing (2.3.0)
172
+ activesupport (>= 5.0.0)
173
+ minitest
114
174
  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)
175
+ rails-html-sanitizer (1.7.1)
176
+ loofah (~> 2.25, >= 2.25.2)
177
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
178
+ railties (7.1.6)
179
+ actionpack (= 7.1.6)
180
+ activesupport (= 7.1.6)
181
+ cgi
182
+ irb
183
+ rackup (>= 1.0.0)
184
+ rake (>= 12.2)
185
+ thor (~> 1.0, >= 1.2.2)
186
+ tsort (>= 0.2)
187
+ zeitwerk (~> 2.6)
188
+ rake (13.4.2)
189
+ rbs (4.2.0)
190
+ logger
191
+ prism (>= 1.6.0)
192
+ tsort
193
+ rdoc (8.0.0)
194
+ erb
195
+ prism (>= 1.6.0)
196
+ rbs (>= 4.0.0)
197
+ tsort
198
+ reline (0.7.0)
199
+ io-console (~> 0.5)
200
+ rspec (3.13.2)
201
+ rspec-core (~> 3.13.0)
202
+ rspec-expectations (~> 3.13.0)
203
+ rspec-mocks (~> 3.13.0)
204
+ rspec-core (3.13.6)
205
+ rspec-support (~> 3.13.0)
206
+ rspec-expectations (3.13.5)
131
207
  diff-lcs (>= 1.2.0, < 2.0)
132
- rspec-support (~> 3.10.0)
133
- rspec-mocks (3.10.2)
208
+ rspec-support (~> 3.13.0)
209
+ rspec-mocks (3.13.8)
134
210
  diff-lcs (>= 1.2.0, < 2.0)
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)
154
- tzinfo (2.0.4)
211
+ rspec-support (~> 3.13.0)
212
+ rspec-rails (7.1.1)
213
+ actionpack (>= 7.0)
214
+ activesupport (>= 7.0)
215
+ railties (>= 7.0)
216
+ rspec-core (~> 3.13)
217
+ rspec-expectations (~> 3.13)
218
+ rspec-mocks (~> 3.13)
219
+ rspec-support (~> 3.13)
220
+ rspec-support (3.13.7)
221
+ securerandom (0.4.1)
222
+ sqlite3 (2.9.6)
223
+ mini_portile2 (~> 2.8.0)
224
+ thor (1.5.0)
225
+ timeout (0.6.1)
226
+ tsort (0.2.0)
227
+ tzinfo (2.0.6)
155
228
  concurrent-ruby (~> 1.0)
156
- websocket-driver (0.7.3)
229
+ websocket-driver (0.8.2)
230
+ base64
157
231
  websocket-extensions (>= 0.1.0)
158
232
  websocket-extensions (0.1.5)
159
- zeitwerk (2.4.2)
233
+ zeitwerk (2.8.3)
160
234
 
161
235
  PLATFORMS
162
236
  ruby
163
237
 
164
238
  DEPENDENCIES
165
- bundler (~> 1.16)
239
+ bundler (~> 2.5)
166
240
  byebug
167
241
  json_api_filter!
168
- rails
242
+ rails (>= 7.1, < 7.2)
169
243
  rake (~> 13.0)
170
244
  rspec (>= 3.0)
171
245
  rspec-rails
172
246
  sqlite3
173
247
 
174
248
  BUNDLED WITH
175
- 1.17.2
249
+ 2.5.11
data/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
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
- [![Maintainability](https://api.codeclimate.com/v1/badges/f620f88131ea9d8ff650/maintainability)](https://codeclimate.com/github/Blaked84/json_api_filter/maintainability)
5
+ [![Maintainability](https://api.codeclimate.com/v1/badges/92a4a44d4af2bfa3b27d/maintainability)](https://codeclimate.com/github/evalmee/json_api_filter/maintainability)
6
6
  [![Gem Version](https://badge.fury.io/rb/json_api_filter.svg)](https://badge.fury.io/rb/json_api_filter)
7
- [![Build Status](https://travis-ci.com/Blaked84/json_api_filter.svg?branch=master)](https://travis-ci.com/Blaked84/json_api_filter)
7
+ [![Build Status](https://travis-ci.com/evalmee/json_api_filter.svg?branch=master)](https://travis-ci.com/evalmee/json_api_filter)
8
8
 
9
9
  ## Installation
10
10
 
@@ -28,7 +28,7 @@ $ gem install json_api_filter
28
28
 
29
29
  ### Quick start
30
30
 
31
- To filter this request `/books?filter[library_id]=1,2&filter[author_id]=12&search=Lord of the ring`
31
+ To filter this request `/books?filter[library_id]=1,2&filter[author_id]=12&search=Lord of the ring&include=users,users.posts`
32
32
 
33
33
  ```ruby
34
34
  class Book < ApplicationController
@@ -36,19 +36,114 @@ class Book < ApplicationController
36
36
  include JsonApiFilter
37
37
  permitted_filters %i[library_id author_id]
38
38
  permitted_searches :user_search
39
-
39
+ permitted_inclusions %i[users users.posts]
40
+
40
41
  def index
41
42
  @books = json_api_filter(Book, params)
43
+ inclusions = json_api_inclusions(params) # returns [:users, :'users.posts']
44
+
45
+ # then use `inclusions` in the serialiser
46
+ BookSerializer.new(@books, include: inclusions).serializable_hash.to_json
42
47
  end
43
-
44
48
  end
45
49
 
46
50
  ```
47
51
 
48
- - `permitted_filters` let you define allowed attributes to filter on (mandatory)
52
+ - `permitted_filters` let you define allowed attributes to filter on (mandatory unless a custom filter is declared)
49
53
  - `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)
54
+ - `permitted_inclusions` let you define the allowed inclusions
50
55
  - `json_api_filter(scope, params)` return an active record relation (`Book::` in this example)
56
+
57
+ ## Custom filters
58
+
59
+ Use `filter` when the public filter name or values do not map directly to a database column. Custom filters are
60
+ permitted by their declaration and do not also belong in `permitted_filters`.
61
+
62
+ ```ruby
63
+ TYPE_MAP = {
64
+ "online" => "exam",
65
+ "paper" => "paper",
66
+ }.freeze
67
+
68
+ filter :type do
69
+ eq do |scope, values|
70
+ mapped_values = values.map { |value| TYPE_MAP[value] }
71
+
72
+ mapped_values.any?(&:nil?) ? scope.none : scope.where(quiz_type: mapped_values)
73
+ end
74
+ end
75
+ ```
76
+
77
+ Direct syntax uses the `eq` handler, so both of these requests call the block above:
78
+
79
+ ```text
80
+ /exams?filter[type]=online
81
+ /exams?filter[type][eq]=online
82
+ ```
83
+
84
+ Values are always passed as an array. Comma-separated values are split before the handler is called, so
85
+ `filter[type]=online,paper` passes `["online", "paper"]`.
86
+
87
+ The supported custom operators are the operators already understood by the gem: `eq`, `ne`, `gt`, `ge`, `lt`, and
88
+ `le`. Only declared operator blocks run. A custom filter never falls back to treating its public name as a database
89
+ column. Every handler must return an Active Record scope.
90
+
91
+ # Use inclusions in serializers
92
+
93
+ # Handling errors
94
+
95
+ If an endpoint does not support the include parameter, it MUST respond with 400 Bad Request to any requests that include it.
96
+
97
+ ```ruby
98
+ class Book < ApplicationController
99
+
100
+ include JsonApiFilter
101
+ permitted_filters %i[library_id author_id]
102
+ permitted_searches :user_search
103
+
104
+ rescue_from JsonApiFilter::MissingPermittedInclusionError, with: :render_400
105
+
106
+ def index
107
+ @books = json_api_filter(Book, params)
108
+ inclusions = json_api_inclusions(params) # raises JsonApiFilter::MissingPermittedInclusionError
109
+ end
51
110
 
111
+ private
112
+
113
+ def render_400(exception)
114
+ render json: exception, status: 400
115
+ end
116
+ end
117
+ ```
118
+
119
+ If a server is unable to identify a relationship path or does not support inclusion of resources from a path, it MUST respond with 400 Bad Request.
120
+ This request should return a 400 status:
121
+
122
+ `/books?filter[library_id]=1,2&filter[author_id]=12&search=Lord of the ring&include=users,users.posts, users.addresses`
123
+
124
+ ```ruby
125
+ class Book < ApplicationController
126
+
127
+ include JsonApiFilter
128
+ permitted_filters %i[library_id author_id]
129
+ permitted_searches :user_search
130
+ permitted_inclusions %i[users users.posts]
131
+
132
+ rescue_from JsonApiFilter::UnknownInclusionsError, with: :render_400
133
+
134
+ def index
135
+ @books = json_api_filter(Book, params)
136
+ inclusions = json_api_inclusions(params) # raises JsonApiFilter::UnknownInclusionsError
137
+ end
138
+
139
+ private
140
+
141
+ def render_400(exception)
142
+ render json: exception, status: 400
143
+ end
144
+ end
145
+ ```
146
+
52
147
  ## Migration from 0.1
53
148
  0.2.x version is not compatible with 0.1
54
149
  In your controller, you will have to replace all occurrences of `attr_filter` as bellow :
@@ -11,8 +11,9 @@ Gem::Specification.new do |spec|
11
11
 
12
12
  spec.summary = "Provide a filter for jsonAPI Controller in Rails"
13
13
  spec.description = ""
14
- spec.homepage = "https://github.com/Blaked84/json_api_filter"
14
+ spec.homepage = "https://github.com/evalmee/json_api_filter"
15
15
  spec.license = "MIT"
16
+ spec.required_ruby_version = ">= 3.3.4"
16
17
 
17
18
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
19
  # to allow pushing to a single host or delete this section to allow pushing to any host.
@@ -20,7 +21,7 @@ Gem::Specification.new do |spec|
20
21
  spec.metadata["allowed_push_host"] = "https://rubygems.org"
21
22
 
22
23
  spec.metadata["homepage_uri"] = spec.homepage
23
- spec.metadata["source_code_uri"] = "https://github.com/Blaked84/json_api_filter"
24
+ spec.metadata["source_code_uri"] = "https://github.com/evalmee/json_api_filter"
24
25
  else
25
26
  raise "RubyGems 2.0 or newer is required to protect against " \
26
27
  "public gem pushes."
@@ -35,12 +36,12 @@ Gem::Specification.new do |spec|
35
36
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
36
37
  spec.require_paths = ["lib"]
37
38
 
38
- spec.add_development_dependency "bundler", "~> 1.16"
39
+ spec.add_development_dependency "bundler", "~> 2.5"
39
40
  spec.add_development_dependency "rake", "~> 13.0"
40
41
  spec.add_development_dependency "rspec", ">= 3.0"
41
42
  spec.add_development_dependency "rspec-rails"
42
43
  spec.add_development_dependency "sqlite3"
43
- spec.add_development_dependency 'rails'
44
+ spec.add_development_dependency 'rails', '>= 7.1', '< 7.2'
44
45
  spec.add_development_dependency 'byebug'
45
46
  spec.add_dependency "activesupport", ">= 3.0.0"
46
- end
47
+ end
@@ -0,0 +1,24 @@
1
+ module JsonApiFilter
2
+ class CustomFilter
3
+ OPERATORS = %i[eq ne gt ge lt le].freeze
4
+
5
+ attr_reader :handlers
6
+
7
+ def initialize(&definition)
8
+ @handlers = {}
9
+ instance_eval(&definition)
10
+ @handlers.freeze
11
+ freeze
12
+ end
13
+
14
+ OPERATORS.each do |operator|
15
+ define_method(operator) do |&handler|
16
+ handlers[operator.to_s] = handler
17
+ end
18
+ end
19
+
20
+ def handler_for(operator)
21
+ handlers[operator.to_s]
22
+ end
23
+ end
24
+ end
@@ -1,16 +1,17 @@
1
1
  module JsonApiFilter
2
2
  class Dispatch
3
3
 
4
- attr_reader :params, :scope, :allowed_filters, :allowed_searches
4
+ attr_reader :params, :scope, :allowed_filters, :allowed_searches, :custom_filters
5
5
 
6
6
  # @param [ActiveRecord::Base] scope
7
7
  # @param [Hash, ActionController::Parameters] params
8
8
  # @param [Array<Symbol>] allowed_filters
9
- def initialize(scope, params, allowed_filters:, allowed_searches:)
9
+ def initialize(scope, params, allowed_filters:, allowed_searches:, custom_filters: {})
10
10
  @params = params
11
11
  @scope = scope
12
12
  @allowed_filters = allowed_filters
13
13
  @allowed_searches = allowed_searches
14
+ @custom_filters = custom_filters
14
15
  end
15
16
 
16
17
  # @return [ActiveRecord_Relation]
@@ -32,6 +33,8 @@ module JsonApiFilter
32
33
  def filters_predicate
33
34
  #TODO: split this method
34
35
  parser_params.fetch('filter', {}).map do |key, value|
36
+ custom_filter = custom_filters[key.to_s]
37
+ next custom_filter_predicate(custom_filter, value) if custom_filter
35
38
  next unless filters.include?(key)
36
39
 
37
40
  if nested_filters.include?(key.to_sym)
@@ -39,19 +42,45 @@ module JsonApiFilter
39
42
  scope,
40
43
  {key => value},
41
44
  association: true
42
- )
45
+ ).predicate
43
46
  end
44
47
 
45
48
  if value.class != ActiveSupport::HashWithIndifferentAccess
46
- next ::JsonApiFilter::FieldFilters::Matcher.new(scope, {key => value})
49
+ next ::JsonApiFilter::FieldFilters::Matcher.new(scope, {key => value}).predicate
47
50
  end
48
51
 
49
52
  ::JsonApiFilter::FieldFilters::Compare.new(
50
53
  scope,
51
54
  {key => value},
52
55
  allowed_searches: allowed_searches
53
- )
54
- end.compact.map(&:predicate).reduce(&:merge)
56
+ ).predicate
57
+ end.compact.reduce(&:merge)
58
+ end
59
+
60
+ def custom_filter_predicate(custom_filter, value)
61
+ predicates = if value.respond_to?(:each_pair)
62
+ value.map do |operator, operator_value|
63
+ apply_custom_filter(custom_filter, operator, operator_value)
64
+ end
65
+ else
66
+ [apply_custom_filter(custom_filter, :eq, value)]
67
+ end
68
+
69
+ predicates.compact.reduce(&:merge)
70
+ end
71
+
72
+ def apply_custom_filter(custom_filter, operator, value)
73
+ handler = custom_filter.handler_for(operator)
74
+ return unless handler
75
+
76
+ handler.call(scope, custom_filter_values(value))
77
+ end
78
+
79
+ def custom_filter_values(value)
80
+ return value if value.is_a?(Array)
81
+ return ::JsonApiFilter::ValueParser.parse(value) if value.is_a?(String)
82
+
83
+ [value]
55
84
  end
56
85
 
57
86
  # @return [ActiveRecord::Base, NilClass]
@@ -1,3 +1,3 @@
1
1
  module JsonApiFilter
2
- VERSION = "0.2"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -1,5 +1,6 @@
1
1
  require "json_api_filter/version"
2
2
  require "json_api_filter/auto_join"
3
+ require "json_api_filter/custom_filter"
3
4
  require "json_api_filter/dispatch"
4
5
  require "json_api_filter/filter_attributes"
5
6
  require "json_api_filter/value_parser"
@@ -19,12 +20,30 @@ module JsonApiFilter
19
20
  end
20
21
  end
21
22
 
23
+ class MissingPermittedInclusionError < ::StandardError
24
+ def message
25
+ "PERMITTED_INCLUSIONS are required"
26
+ end
27
+ end
28
+
29
+ class UnknownInclusionsError < ::StandardError
30
+ attr_reader :item
31
+ def initialize(item)
32
+ super(message)
33
+ @item = item
34
+ end
35
+
36
+ def message
37
+ "Unable to identify #{item} as a relationship path or inclusion"
38
+ end
39
+ end
40
+
22
41
  extend ::ActiveSupport::Concern
23
42
  included do
24
43
 
25
44
  # @param [ActiveRecord::Base] scope
26
45
  def json_api_filter(scope, query_params = params)
27
- unless self.class.json_api_permitted_filters.present?
46
+ unless self.class.json_api_permitted_filters.present? || self.class.json_api_custom_filters.present?
28
47
  raise ::JsonApiFilter::MissingPermittedFilterError
29
48
  end
30
49
 
@@ -32,10 +51,22 @@ module JsonApiFilter
32
51
  scope,
33
52
  query_params,
34
53
  allowed_filters: self.class.json_api_permitted_filters,
35
- allowed_searches: self.class.json_api_permitted_searches
54
+ allowed_searches: self.class.json_api_permitted_searches,
55
+ custom_filters: self.class.json_api_custom_filters
36
56
  ).process
37
57
  end
38
58
 
59
+ def json_api_inclusions(params)
60
+ inclusions_params = params.fetch(:include, "").split(",").map(&:to_sym).uniq
61
+ unknown_inclusion = inclusions_params.find { |resource| json_api_permitted_inclusions.exclude?(resource) }
62
+
63
+ # If a server is unable to identify a relationship path or does not support inclusion of resources from a path,
64
+ # it MUST respond with 400 Bad Request.
65
+ raise UnknownInclusionsError, unknown_inclusion if unknown_inclusion.present?
66
+
67
+ inclusions_params
68
+ end
69
+
39
70
  def self.permitted_filters(val)
40
71
  define_singleton_method(:json_api_permitted_filters) do
41
72
  val
@@ -46,6 +77,20 @@ module JsonApiFilter
46
77
  []
47
78
  end
48
79
 
80
+ def self.filter(name, &definition)
81
+ filters = json_api_custom_filters.merge(
82
+ name.to_s => ::JsonApiFilter::CustomFilter.new(&definition)
83
+ ).freeze
84
+
85
+ define_singleton_method(:json_api_custom_filters) do
86
+ filters
87
+ end
88
+ end
89
+
90
+ def self.json_api_custom_filters
91
+ {}.freeze
92
+ end
93
+
49
94
  def self.permitted_searches(global, **columns)
50
95
  define_singleton_method(:json_api_permitted_searches) do
51
96
  { global: global, columns: columns }
@@ -55,5 +100,21 @@ module JsonApiFilter
55
100
  def self.json_api_permitted_searches
56
101
  {}
57
102
  end
103
+
104
+ def self.permitted_inclusions(inclusions)
105
+ define_singleton_method(:json_api_permitted_inclusions) do
106
+ inclusions
107
+ end
108
+ end
109
+
110
+ # If an endpoint does not support the include parameter,
111
+ # it MUST respond with 400 Bad Request to any requests that include it.
112
+ def self.json_api_permitted_inclusions
113
+ raise MissingPermittedInclusionError
114
+ end
115
+
116
+ def json_api_permitted_inclusions
117
+ self.class.json_api_permitted_inclusions
118
+ end
58
119
  end
59
120
  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.2'
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Blaked
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-05-03 00:00:00.000000000 Z
11
+ date: 2026-09-09 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: '1.16'
19
+ version: '2.5'
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: '1.16'
26
+ version: '2.5'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -86,14 +86,20 @@ dependencies:
86
86
  requirements:
87
87
  - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: '0'
89
+ version: '7.1'
90
+ - - "<"
91
+ - !ruby/object:Gem::Version
92
+ version: '7.2'
90
93
  type: :development
91
94
  prerelease: false
92
95
  version_requirements: !ruby/object:Gem::Requirement
93
96
  requirements:
94
97
  - - ">="
95
98
  - !ruby/object:Gem::Version
96
- version: '0'
99
+ version: '7.1'
100
+ - - "<"
101
+ - !ruby/object:Gem::Version
102
+ version: '7.2'
97
103
  - !ruby/object:Gem::Dependency
98
104
  name: byebug
99
105
  requirement: !ruby/object:Gem::Requirement
@@ -129,8 +135,10 @@ executables: []
129
135
  extensions: []
130
136
  extra_rdoc_files: []
131
137
  files:
138
+ - ".github/workflows/gem-push.yml"
132
139
  - ".gitignore"
133
140
  - ".rspec"
141
+ - ".ruby-version"
134
142
  - ".travis.yml"
135
143
  - CODE_OF_CONDUCT.md
136
144
  - Gemfile
@@ -143,6 +151,7 @@ files:
143
151
  - json_api_filter.gemspec
144
152
  - lib/json_api_filter.rb
145
153
  - lib/json_api_filter/auto_join.rb
154
+ - lib/json_api_filter/custom_filter.rb
146
155
  - lib/json_api_filter/dispatch.rb
147
156
  - lib/json_api_filter/field_filters/base.rb
148
157
  - lib/json_api_filter/field_filters/compare.rb
@@ -153,14 +162,14 @@ files:
153
162
  - lib/json_api_filter/filter_attributes.rb
154
163
  - lib/json_api_filter/value_parser.rb
155
164
  - lib/json_api_filter/version.rb
156
- homepage: https://github.com/Blaked84/json_api_filter
165
+ homepage: https://github.com/evalmee/json_api_filter
157
166
  licenses:
158
167
  - MIT
159
168
  metadata:
160
169
  allowed_push_host: https://rubygems.org
161
- homepage_uri: https://github.com/Blaked84/json_api_filter
162
- source_code_uri: https://github.com/Blaked84/json_api_filter
163
- post_install_message:
170
+ homepage_uri: https://github.com/evalmee/json_api_filter
171
+ source_code_uri: https://github.com/evalmee/json_api_filter
172
+ post_install_message:
164
173
  rdoc_options: []
165
174
  require_paths:
166
175
  - lib
@@ -168,15 +177,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
168
177
  requirements:
169
178
  - - ">="
170
179
  - !ruby/object:Gem::Version
171
- version: '0'
180
+ version: 3.3.4
172
181
  required_rubygems_version: !ruby/object:Gem::Requirement
173
182
  requirements:
174
183
  - - ">="
175
184
  - !ruby/object:Gem::Version
176
185
  version: '0'
177
186
  requirements: []
178
- rubygems_version: 3.0.3
179
- signing_key:
187
+ rubygems_version: 3.5.11
188
+ signing_key:
180
189
  specification_version: 4
181
190
  summary: Provide a filter for jsonAPI Controller in Rails
182
191
  test_files: []