json_api_filter 0.1.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b8ea65bebb5e2456a2009eea40099f2f000a4bc9c83ed79d3d4207c7b86cbdf
4
- data.tar.gz: eaaa75be388a45dd56755d47a94956597befeb4db09bd15de5ec8832b047fdef
3
+ metadata.gz: a0e5c959706685cf6cc047d4824c7ed0e0c35778b6b952aa3b305e5e0eb40f13
4
+ data.tar.gz: 3a87503e2341ae745d0ec6b5154049c95b0a28b527aeb8903ed000e53685bf3b
5
5
  SHA512:
6
- metadata.gz: 2c383c8c9602a81549fcc45b016b262e07d99c16c90239fcd390057503dbb3773a3ed07ccf86b06157a65b85f0d86e1742dd7465ab9c1c5bd46a0a48c46e8790
7
- data.tar.gz: 3c52afa46d6f74466f561af5a7f896a77af6b41440c8b2cac75f11c87a19f1fe26c61bdac86521853dddc8d415b257179a7d65be394083444ad2c6d82b0b4134
6
+ metadata.gz: fee62197e5d3814185e35f7935b76daba4c96dcabd2e655e250512f3267e8295512fffe16301df3b18b089c4eb5b5cbed1a120793dd60518929aa666ba16b5e2
7
+ data.tar.gz: c183c0a031bac479dfa66baf3cb90a9ef6735719c7dc61a12fcbda50412a0239e5dcd6ca4ebd3ddffae23588f9cd90793123a79cd2a44107723a9f869b5d4b2f
data/.gitignore CHANGED
@@ -9,4 +9,7 @@
9
9
 
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
- /.idea/
12
+ /.idea/
13
+ /spec/dummy/db/development.sqlite3
14
+ /spec/dummy/db/test.sqlite3
15
+ /spec/dummy/log/
data/.travis.yml CHANGED
@@ -5,3 +5,6 @@ cache: bundler
5
5
  rvm:
6
6
  - 2.6.3
7
7
  before_install: gem install bundler -v 1.17.2
8
+ script:
9
+ - (cd spec/dummy && RAILS_ENV=test bundle exec rails db:setup)
10
+ - bundle exec rspec
data/Gemfile.lock CHANGED
@@ -1,39 +1,161 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- json_api_filter (0.1.1)
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
- activesupport (6.1.3)
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
- diff-lcs (1.3)
18
- i18n (1.8.9)
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
- rake (13.0.1)
22
- rspec (3.9.0)
23
- rspec-core (~> 3.9.0)
24
- rspec-expectations (~> 3.9.0)
25
- rspec-mocks (~> 3.9.0)
26
- rspec-core (3.9.0)
27
- rspec-support (~> 3.9.0)
28
- rspec-expectations (3.9.0)
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.9.0)
31
- rspec-mocks (3.9.0)
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.9.0)
34
- rspec-support (3.9.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)
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 (~> 3.0)
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
+ [![Maintainability](https://api.codeclimate.com/v1/badges/f620f88131ea9d8ff650/maintainability)](https://codeclimate.com/github/Blaked84/json_api_filter/maintainability)
5
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)
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
- $ bundle
18
+ ```bash
19
+ $ bundle
20
+ ```
18
21
 
19
22
  Or install it yourself as:
20
-
21
- $ gem install json_api_filter
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.all.where(attr_filter(params))
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
- - `filter_by_attr(params)` return a hash with the filter from the request params
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.
@@ -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", "~> 3.0"
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
@@ -1,6 +1,14 @@
1
1
  require "json_api_filter/version"
2
- require "json_api_filter/filters"
3
- require "json_api_filter/filter_by"
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
- def attr_filter(query_params = params)
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::Filters.new(
23
- self.class.json_api_permitted_filters,
24
- query_params
25
- ).to_hash
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,12 @@
1
+ module JsonApiFilter
2
+ module FieldFilters
3
+ class Searcher < Base
4
+
5
+ # @return [ActiveRecord_Relation]
6
+ def predicate
7
+ scope.send(values.keys.first, values.values.first)
8
+ end
9
+
10
+ end
11
+ end
12
+ 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
@@ -1,3 +1,3 @@
1
1
  module JsonApiFilter
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2"
3
3
  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.1.1
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-25 00:00:00.000000000 Z
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/filter_by.rb
90
- - lib/json_api_filter/filters.rb
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
-