fetcheable_on_api 0.1.7 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +123 -2
- data/lib/fetcheable_on_api.rb +14 -8
- data/lib/fetcheable_on_api/filtreable.rb +84 -7
- data/lib/fetcheable_on_api/pagineable.rb +1 -1
- data/lib/fetcheable_on_api/sortable.rb +1 -1
- data/lib/fetcheable_on_api/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d557420a13e2668f210f92dd25263770b8345dc82b1cc68ec49171028a708b0c
|
4
|
+
data.tar.gz: e4c9edbd515862b132c67d6a8cefd4a2e3d76283d931ffa7d9f43b436f271ef3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5154c9bf6a13a635cbad72e6ab9f3a793598e3082c92714d397ef3f81687df8d69f83b6ab80c723cdb544f19e50479e79a2e43519e689ec0102f92a021c9c4bc
|
7
|
+
data.tar.gz: 8de8a403e545f21477c2d2124d0c92a765ef1eae194d3635fc233352c4157086aaf03a4864987e571dfddecdbf70e1ce68c5182efb0e42aef99d1f7831bc01f8
|
data/README.md
CHANGED
@@ -433,10 +433,131 @@ $ curl -X GET \
|
|
433
433
|
]
|
434
434
|
```
|
435
435
|
|
436
|
-
Currently
|
436
|
+
Currently 33 predicates are supported ([more details here](https://github.com/fabienpiette/fetcheable_on_api/wiki/Predicates)):
|
437
437
|
|
438
|
-
+ `:
|
438
|
+
+ `:between`
|
439
|
+
+ `:does_not_match`
|
440
|
+
+ `:does_not_match_all`
|
441
|
+
+ `:does_not_match_any`
|
439
442
|
+ `:eq` which matches the parameter with the SQL fragment `= 'foo'`.
|
443
|
+
+ `:eq_all`
|
444
|
+
+ `:eq_any`
|
445
|
+
+ `:gt`
|
446
|
+
+ `:gt_all`
|
447
|
+
+ `:gt_any`
|
448
|
+
+ `:gteq`
|
449
|
+
+ `:gteq_all`
|
450
|
+
+ `:gteq_any`
|
451
|
+
+ `:ilike` which is the default behaviour and will match the parameter with the SQL fragment `ILIKE '%foo%'`.
|
452
|
+
+ `:in`
|
453
|
+
+ `:in_all`
|
454
|
+
+ `:in_any`
|
455
|
+
+ `:lt`
|
456
|
+
+ `:lt_all`
|
457
|
+
+ `:lt_any`
|
458
|
+
+ `:lteq`
|
459
|
+
+ `:lteq_all`
|
460
|
+
+ `:lteq_any`
|
461
|
+
+ `:matches`
|
462
|
+
+ `:matches_all`
|
463
|
+
+ `:matches_any`
|
464
|
+
+ `:not_between`
|
465
|
+
+ `:not_eq`
|
466
|
+
+ `:not_eq_all`
|
467
|
+
+ `:not_eq_any`
|
468
|
+
+ `:not_in`
|
469
|
+
+ `:not_in_all`
|
470
|
+
+ `:not_in_any`
|
471
|
+
|
472
|
+
+ `lamdba` wich take two arguments: a collection and a value, then return an Arel predicate.
|
473
|
+
```ruby
|
474
|
+
filter_by :name, with: -> (collection, value) do
|
475
|
+
collection.arel_table[:first_name].matches("%#{value}%").or(
|
476
|
+
collection.arel_table[:last_name].matches("%#{value}%"),
|
477
|
+
)
|
478
|
+
end
|
479
|
+
```
|
480
|
+
|
481
|
+
You can also use an array as a parameter for some predicate
|
482
|
+
|
483
|
+
```ruby
|
484
|
+
class QuestionsController < ActionController::Base
|
485
|
+
#
|
486
|
+
# FetcheableOnApi
|
487
|
+
#
|
488
|
+
filter_by :id, with: :between
|
489
|
+
|
490
|
+
# GET /questions
|
491
|
+
def index
|
492
|
+
questions = apply_fetcheable(Question.includes(:answer).all)
|
493
|
+
render json: questions
|
494
|
+
end
|
495
|
+
end
|
496
|
+
```
|
497
|
+
|
498
|
+
```bash
|
499
|
+
curl -X GET \
|
500
|
+
'http://localhost:3000/questions?filter[id]=[1]'
|
501
|
+
|
502
|
+
[
|
503
|
+
{
|
504
|
+
"id": 1,
|
505
|
+
"position": 1,
|
506
|
+
"content": "Je peux boire ou cuisiner avec l'eau de pluie ?",
|
507
|
+
"answer": "Faux : l'eau de pluie que vous récupérez est strictement interdite pour une consommation alimentaire car elle n'est pas potable.\nVous ne devez donc pas la boire, ni l'utiliser pour cuisiner ou laver la vaisselle.\n",
|
508
|
+
"base_value": false
|
509
|
+
}
|
510
|
+
]
|
511
|
+
```
|
512
|
+
|
513
|
+
Date manipulation is a special case and can be solved by specifically indicating the expected format for the parameter.
|
514
|
+
|
515
|
+
```ruby
|
516
|
+
class QuestionsController < ActionController::Base
|
517
|
+
#
|
518
|
+
# FetcheableOnApi
|
519
|
+
#
|
520
|
+
filter_by :created_at,
|
521
|
+
with: :between,
|
522
|
+
format: :datetime
|
523
|
+
|
524
|
+
# GET /questions
|
525
|
+
def index
|
526
|
+
questions = apply_fetcheable(Question.includes(:answer).all)
|
527
|
+
render json: questions
|
528
|
+
end
|
529
|
+
end
|
530
|
+
```
|
531
|
+
|
532
|
+
```bash
|
533
|
+
curl -X GET \
|
534
|
+
'http://localhost:3000/questions?filter[created_at]=[1541428932,1541428933]'
|
535
|
+
```
|
536
|
+
|
537
|
+
By default the format used is epoch time, but you can redefine it by overriding the method `foa_string_to_datetime`
|
538
|
+
|
539
|
+
```ruby
|
540
|
+
class QuestionsController < ActionController::Base
|
541
|
+
#
|
542
|
+
# FetcheableOnApi
|
543
|
+
#
|
544
|
+
filter_by :created_at,
|
545
|
+
with: :between,
|
546
|
+
format: :datetime
|
547
|
+
|
548
|
+
# GET /questions
|
549
|
+
def index
|
550
|
+
questions = apply_fetcheable(Question.includes(:answer).all)
|
551
|
+
render json: questions
|
552
|
+
end
|
553
|
+
|
554
|
+
protected
|
555
|
+
|
556
|
+
def foa_string_to_datetime(string)
|
557
|
+
DateTime.strptime(string, '%s')
|
558
|
+
end
|
559
|
+
end
|
560
|
+
```
|
440
561
|
|
441
562
|
And that's all !
|
442
563
|
|
data/lib/fetcheable_on_api.rb
CHANGED
@@ -6,6 +6,7 @@ require 'fetcheable_on_api/pagineable'
|
|
6
6
|
require 'fetcheable_on_api/sortable'
|
7
7
|
require 'fetcheable_on_api/version'
|
8
8
|
require 'active_support'
|
9
|
+
require 'date'
|
9
10
|
|
10
11
|
module FetcheableOnApi
|
11
12
|
#
|
@@ -26,7 +27,8 @@ module FetcheableOnApi
|
|
26
27
|
#
|
27
28
|
# Supports
|
28
29
|
#
|
29
|
-
ArgumentError
|
30
|
+
ArgumentError = Class.new(ArgumentError)
|
31
|
+
NotImplementedError = Class.new(NotImplementedError)
|
30
32
|
|
31
33
|
#
|
32
34
|
# Public class methods
|
@@ -55,25 +57,29 @@ module FetcheableOnApi
|
|
55
57
|
apply_pagination(collection)
|
56
58
|
end
|
57
59
|
|
58
|
-
def
|
60
|
+
def foa_valid_parameters!(*keys, foa_permitted_types: foa_default_permitted_types)
|
59
61
|
raise FetcheableOnApi::ArgumentError.new(
|
60
62
|
"Incorrect type #{params.dig(*keys).class} for params #{keys}"
|
61
|
-
) unless
|
63
|
+
) unless foa_valid_params_types(*keys, foa_permitted_types: foa_permitted_types)
|
62
64
|
end
|
63
65
|
|
64
|
-
def
|
65
|
-
|
66
|
-
res ||
|
66
|
+
def foa_valid_params_types(*keys, foa_permitted_types: foa_default_permitted_types)
|
67
|
+
foa_permitted_types.inject(false) do |res, type|
|
68
|
+
res || foa_valid_params_type(params.dig(*keys), type)
|
67
69
|
end
|
68
70
|
end
|
69
71
|
|
70
|
-
def
|
72
|
+
def foa_valid_params_type(value, type)
|
71
73
|
value.is_a?(type)
|
72
74
|
end
|
73
75
|
|
74
|
-
def
|
76
|
+
def foa_default_permitted_types
|
75
77
|
[ActionController::Parameters, Hash]
|
76
78
|
end
|
79
|
+
|
80
|
+
def foa_string_to_datetime(string)
|
81
|
+
DateTime.strptime(string, '%s')
|
82
|
+
end
|
77
83
|
end
|
78
84
|
|
79
85
|
ActiveSupport.on_load :action_controller do
|
@@ -12,7 +12,7 @@ module FetcheableOnApi
|
|
12
12
|
def self.included(base)
|
13
13
|
base.class_eval do
|
14
14
|
extend ClassMethods
|
15
|
-
class_attribute :filters_configuration, :
|
15
|
+
class_attribute :filters_configuration, instance_writer: false
|
16
16
|
self.filters_configuration = {}
|
17
17
|
end
|
18
18
|
end
|
@@ -21,7 +21,7 @@ module FetcheableOnApi
|
|
21
21
|
def filter_by(*attrs)
|
22
22
|
options = attrs.extract_options!
|
23
23
|
options.symbolize_keys!
|
24
|
-
options.assert_valid_keys(:as, :class_name, :with)
|
24
|
+
options.assert_valid_keys(:as, :class_name, :with, :format)
|
25
25
|
|
26
26
|
self.filters_configuration = filters_configuration.dup
|
27
27
|
|
@@ -44,27 +44,104 @@ module FetcheableOnApi
|
|
44
44
|
#
|
45
45
|
protected
|
46
46
|
|
47
|
+
def try_parse_array(values, format)
|
48
|
+
array = JSON.parse(values)
|
49
|
+
array.map! { |el| foa_string_to_datetime(el.to_s) } if format == :datetime
|
50
|
+
|
51
|
+
[array]
|
52
|
+
rescue JSON::ParserError
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
47
56
|
def apply_filters(collection)
|
48
57
|
return collection if params[:filter].blank?
|
49
|
-
|
58
|
+
foa_valid_parameters!(:filter)
|
50
59
|
|
51
60
|
filter_params = params.require(:filter)
|
52
61
|
.permit(filters_configuration.keys)
|
53
62
|
.to_hash
|
54
63
|
|
55
64
|
filtering = filter_params.map do |column, values|
|
56
|
-
|
65
|
+
format = filters_configuration[column.to_sym].fetch(:format, :string)
|
66
|
+
elements = try_parse_array(values, format)
|
67
|
+
elements ||= values.split(',')
|
68
|
+
|
69
|
+
elements.map do |value|
|
57
70
|
column_name = filters_configuration[column.to_sym].fetch(:as, column)
|
58
71
|
klass = filters_configuration[column.to_sym].fetch(:class_name, collection.klass)
|
59
72
|
predicate = filters_configuration[column.to_sym].fetch(:with, :ilike)
|
60
73
|
|
61
74
|
case predicate
|
62
|
-
when :
|
63
|
-
klass.arel_table[column_name].
|
75
|
+
when :between
|
76
|
+
klass.arel_table[column_name].between(value.first..value.last)
|
77
|
+
when :does_not_match
|
78
|
+
klass.arel_table[column_name].does_not_match("%#{value}%")
|
79
|
+
when :does_not_match_all
|
80
|
+
klass.arel_table[column_name].does_not_match_all(value)
|
81
|
+
when :does_not_match_any
|
82
|
+
klass.arel_table[column_name].does_not_match_any(value)
|
64
83
|
when :eq
|
65
84
|
klass.arel_table[column_name].eq(value)
|
85
|
+
when :eq_all
|
86
|
+
klass.arel_table[column_name].eq_all(value)
|
87
|
+
when :eq_any
|
88
|
+
klass.arel_table[column_name].eq_any(value)
|
89
|
+
when :gt
|
90
|
+
klass.arel_table[column_name].gt(value)
|
91
|
+
when :gt_all
|
92
|
+
klass.arel_table[column_name].gt_all(value)
|
93
|
+
when :gt_any
|
94
|
+
klass.arel_table[column_name].gt_any(value)
|
95
|
+
when :gteq
|
96
|
+
klass.arel_table[column_name].gteq(value)
|
97
|
+
when :gteq_all
|
98
|
+
klass.arel_table[column_name].gteq_all(value)
|
99
|
+
when :gteq_any
|
100
|
+
klass.arel_table[column_name].gteq_any(value)
|
101
|
+
when :in
|
102
|
+
klass.arel_table[column_name].in(value)
|
103
|
+
when :in_all
|
104
|
+
klass.arel_table[column_name].in_all(value)
|
105
|
+
when :in_any
|
106
|
+
klass.arel_table[column_name].in_any(value)
|
107
|
+
when :lt
|
108
|
+
klass.arel_table[column_name].lt(value)
|
109
|
+
when :lt_all
|
110
|
+
klass.arel_table[column_name].lt_all(value)
|
111
|
+
when :lt_any
|
112
|
+
klass.arel_table[column_name].lt_any(value)
|
113
|
+
when :lteq
|
114
|
+
klass.arel_table[column_name].lteq(value)
|
115
|
+
when :lteq_all
|
116
|
+
klass.arel_table[column_name].lteq_all(value)
|
117
|
+
when :lteq_any
|
118
|
+
klass.arel_table[column_name].lteq_any(value)
|
119
|
+
when :ilike
|
120
|
+
klass.arel_table[column_name].matches("%#{value}%")
|
121
|
+
when :matches
|
122
|
+
klass.arel_table[column_name].matches(value)
|
123
|
+
when :matches_all
|
124
|
+
klass.arel_table[column_name].matches_all(value)
|
125
|
+
when :matches_any
|
126
|
+
klass.arel_table[column_name].matches_any(value)
|
127
|
+
when :not_between
|
128
|
+
klass.arel_table[column_name].not_between(value.first..value.last)
|
129
|
+
when :not_eq
|
130
|
+
klass.arel_table[column_name].not_eq(value)
|
131
|
+
when :not_eq_all
|
132
|
+
klass.arel_table[column_name].not_eq_all(value)
|
133
|
+
when :not_eq_any
|
134
|
+
klass.arel_table[column_name].not_eq_any(value)
|
135
|
+
when :not_in
|
136
|
+
klass.arel_table[column_name].not_in(value)
|
137
|
+
when :not_in_all
|
138
|
+
klass.arel_table[column_name].not_in_all(value)
|
139
|
+
when :not_in_any
|
140
|
+
klass.arel_table[column_name].not_in_any(value)
|
66
141
|
else
|
67
|
-
raise ArgumentError, "unsupported predicate `#{predicate}`"
|
142
|
+
raise ArgumentError, "unsupported predicate `#{predicate}`" unless predicate.respond_to?(:call)
|
143
|
+
|
144
|
+
predicate.call(collection, value)
|
68
145
|
end
|
69
146
|
end.inject(:or)
|
70
147
|
end
|
@@ -49,7 +49,7 @@ module FetcheableOnApi
|
|
49
49
|
|
50
50
|
def apply_sort(collection)
|
51
51
|
return collection if params[:sort].blank?
|
52
|
-
|
52
|
+
foa_valid_parameters!(:sort, permitted_types: [String])
|
53
53
|
|
54
54
|
ordering = {}
|
55
55
|
sorted_params = params[:sort].split(',')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fetcheable_on_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Fabien
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-12-
|
11
|
+
date: 2018-12-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|