fetcheable_on_api 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -3
- data/lib/fetcheable_on_api.rb +26 -5
- data/lib/fetcheable_on_api/configuration.rb +1 -0
- data/lib/fetcheable_on_api/filtreable.rb +35 -35
- data/lib/fetcheable_on_api/pagineable.rb +21 -8
- data/lib/fetcheable_on_api/sortable.rb +46 -17
- data/lib/fetcheable_on_api/version.rb +1 -1
- data/lib/generators/fetcheable_on_api/install_generator.rb +5 -3
- 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: 936c435eaac09ab3f50c5fe2fc650df5f857f99077a3a7cd24c9a7cbfa16bbeb
|
4
|
+
data.tar.gz: 5f029dd6d1fe43c945f0740b947ce0242444a6d6b21a556bc93c6c43295f02fd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e11946ac5da7685b8a10e5638fd05c9dd5d55f5cf95309ccd2398e4b597ccd324ce68b3bdb307b8ed305500797f8a758af2c1b805ffdd671e7ad3ca407810bc
|
7
|
+
data.tar.gz: f60417bd41596796aba7191c24a5dcd2b077945b50ae11dccee646a8d0c6feb539b7b8aa5198e96b83b6eb19c0079f36f1ffefd28d83baadbeb21b37adeb2156
|
data/Gemfile.lock
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
fetcheable_on_api (0.
|
4
|
+
fetcheable_on_api (0.2.2)
|
5
5
|
activesupport (>= 4.1)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
|
-
activesupport (5.2.
|
10
|
+
activesupport (5.2.2)
|
11
11
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
12
12
|
i18n (>= 0.7, < 2)
|
13
13
|
minitest (~> 5.1)
|
@@ -63,4 +63,4 @@ DEPENDENCIES
|
|
63
63
|
rubocop (~> 0.59.2)
|
64
64
|
|
65
65
|
BUNDLED WITH
|
66
|
-
1.
|
66
|
+
1.17.1
|
data/lib/fetcheable_on_api.rb
CHANGED
@@ -8,10 +8,16 @@ require 'fetcheable_on_api/version'
|
|
8
8
|
require 'active_support'
|
9
9
|
require 'date'
|
10
10
|
|
11
|
+
# Detects params from url and apply
|
12
|
+
# filters/sort/paginations to your classes.
|
11
13
|
module FetcheableOnApi
|
12
14
|
#
|
13
15
|
# Configuration
|
14
16
|
#
|
17
|
+
# Configures global settings for FetcheableOnApi
|
18
|
+
# FetcheableOnApi.configure do |config|
|
19
|
+
# config.pagination_default_size = 25
|
20
|
+
# end
|
15
21
|
class << self
|
16
22
|
attr_accessor :configuration
|
17
23
|
end
|
@@ -50,6 +56,7 @@ module FetcheableOnApi
|
|
50
56
|
#
|
51
57
|
protected
|
52
58
|
|
59
|
+
# Apply filters, sort and page on a collection.
|
53
60
|
def apply_fetcheable(collection)
|
54
61
|
collection = apply_filters(collection)
|
55
62
|
collection = apply_sort(collection)
|
@@ -57,26 +64,40 @@ module FetcheableOnApi
|
|
57
64
|
apply_pagination(collection)
|
58
65
|
end
|
59
66
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
67
|
+
# Checks if the type of arguments is included in the permitted types
|
68
|
+
def foa_valid_parameters!(
|
69
|
+
*keys, foa_permitted_types: foa_default_permitted_types
|
70
|
+
)
|
71
|
+
return if foa_valid_params_types(
|
72
|
+
*keys,
|
73
|
+
foa_permitted_types: foa_permitted_types
|
74
|
+
)
|
75
|
+
|
76
|
+
raise FetcheableOnApi::ArgumentError,
|
77
|
+
"Incorrect type #{params.dig(*keys).class} for params #{keys}"
|
64
78
|
end
|
65
79
|
|
66
|
-
def foa_valid_params_types(
|
80
|
+
def foa_valid_params_types(
|
81
|
+
*keys, foa_permitted_types: foa_default_permitted_types
|
82
|
+
)
|
67
83
|
foa_permitted_types.inject(false) do |res, type|
|
68
84
|
res || foa_valid_params_type(params.dig(*keys), type)
|
69
85
|
end
|
70
86
|
end
|
71
87
|
|
88
|
+
# Returns true if class is the class of value,
|
89
|
+
# or if class is one of the superclasses of value
|
90
|
+
# or modules included in value.
|
72
91
|
def foa_valid_params_type(value, type)
|
73
92
|
value.is_a?(type)
|
74
93
|
end
|
75
94
|
|
95
|
+
# Types allowed by default.
|
76
96
|
def foa_default_permitted_types
|
77
97
|
[ActionController::Parameters, Hash]
|
78
98
|
end
|
79
99
|
|
100
|
+
# Convert string to datetime.
|
80
101
|
def foa_string_to_datetime(string)
|
81
102
|
DateTime.strptime(string, '%s')
|
82
103
|
end
|
@@ -1,31 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module FetcheableOnApi
|
4
|
+
# Applying the filter to a collection.
|
4
5
|
module Filtreable
|
5
6
|
#
|
6
7
|
# Supports
|
7
8
|
#
|
8
|
-
PREDICATES_WITH_ARRAY = [
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
9
|
+
PREDICATES_WITH_ARRAY = %i[
|
10
|
+
does_not_match_all
|
11
|
+
does_not_match_any
|
12
|
+
eq_all
|
13
|
+
eq_any
|
14
|
+
gt_all
|
15
|
+
gt_any
|
16
|
+
gteq_all
|
17
|
+
gteq_any
|
18
|
+
in_all
|
19
|
+
in_any
|
20
|
+
lt_all
|
21
|
+
lt_any
|
22
|
+
lteq_all
|
23
|
+
lteq_any
|
24
|
+
matches_all
|
25
|
+
matches_any
|
26
|
+
not_eq_all
|
27
|
+
not_eq_any
|
28
|
+
not_in_all
|
29
|
+
not_in_any
|
29
30
|
].freeze
|
30
31
|
|
31
32
|
#
|
@@ -39,6 +40,7 @@ module FetcheableOnApi
|
|
39
40
|
end
|
40
41
|
end
|
41
42
|
|
43
|
+
# Detects url parameters and applies the filter
|
42
44
|
module ClassMethods
|
43
45
|
def filter_by(*attrs)
|
44
46
|
options = attrs.extract_options!
|
@@ -52,7 +54,7 @@ module FetcheableOnApi
|
|
52
54
|
as: options[:as] || attr
|
53
55
|
}
|
54
56
|
|
55
|
-
filters_configuration[attr]
|
57
|
+
filters_configuration[attr].merge!(options)
|
56
58
|
end
|
57
59
|
end
|
58
60
|
end
|
@@ -66,24 +68,16 @@ module FetcheableOnApi
|
|
66
68
|
#
|
67
69
|
protected
|
68
70
|
|
69
|
-
# def
|
70
|
-
# array
|
71
|
-
# array.map! { |el| foa_string_to_datetime(el.to_s) } if format == :datetime
|
72
|
-
|
73
|
-
# [array]
|
74
|
-
# rescue JSON::ParserError
|
75
|
-
# nil
|
71
|
+
# def convert_to_datetime(array)
|
72
|
+
# array.map { |el| foa_string_to_datetime(el.to_s) }
|
76
73
|
# end
|
77
74
|
|
78
|
-
def convert_to_datetime(array)
|
79
|
-
array.map { |el| foa_string_to_datetime(el.to_s) }
|
80
|
-
end
|
81
|
-
|
82
75
|
def valid_keys
|
83
76
|
keys = filters_configuration.keys
|
84
77
|
keys.each_with_index do |key, index|
|
85
78
|
predicate = filters_configuration[key.to_sym].fetch(:with, :ilike)
|
86
|
-
next if predicate.respond_to?(:call) ||
|
79
|
+
next if predicate.respond_to?(:call) ||
|
80
|
+
PREDICATES_WITH_ARRAY.exclude?(predicate.to_sym)
|
87
81
|
|
88
82
|
keys[index] = { key => [] }
|
89
83
|
end
|
@@ -93,6 +87,7 @@ module FetcheableOnApi
|
|
93
87
|
|
94
88
|
def apply_filters(collection)
|
95
89
|
return collection if params[:filter].blank?
|
90
|
+
|
96
91
|
foa_valid_parameters!(:filter)
|
97
92
|
|
98
93
|
filter_params = params.require(:filter)
|
@@ -118,6 +113,7 @@ module FetcheableOnApi
|
|
118
113
|
collection.where(filtering.flatten.compact.inject(:and))
|
119
114
|
end
|
120
115
|
|
116
|
+
# Apply arel predicate on collection
|
121
117
|
def predicates(predicate, collection, klass, column_name, value)
|
122
118
|
case predicate
|
123
119
|
when :between
|
@@ -187,12 +183,16 @@ module FetcheableOnApi
|
|
187
183
|
when :not_in_any
|
188
184
|
klass.arel_table[column_name].not_in_any(value)
|
189
185
|
else
|
190
|
-
|
186
|
+
unless predicate.respond_to?(:call)
|
187
|
+
raise ArgumentError,
|
188
|
+
"unsupported predicate `#{predicate}`"
|
189
|
+
end
|
191
190
|
|
192
191
|
predicate.call(collection, value)
|
193
192
|
end
|
194
193
|
end
|
195
194
|
|
195
|
+
# Types allowed by default for filter action.
|
196
196
|
def foa_default_permitted_types
|
197
197
|
[ActionController::Parameters, Hash, Array]
|
198
198
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module FetcheableOnApi
|
4
|
+
# Application of a pagination on a collection
|
4
5
|
module Pagineable
|
5
6
|
#
|
6
7
|
# Supports
|
@@ -21,22 +22,34 @@ module FetcheableOnApi
|
|
21
22
|
|
22
23
|
def apply_pagination(collection)
|
23
24
|
return collection if params[:page].blank?
|
25
|
+
|
24
26
|
foa_valid_parameters!(:page)
|
25
27
|
|
26
|
-
limit =
|
27
|
-
|
28
|
-
FetcheableOnApi.configuration.pagination_default_size
|
29
|
-
).to_i
|
28
|
+
limit, offset, count, page = extract_pagination_informations(collection)
|
29
|
+
define_header_pagination(limit, count, page)
|
30
30
|
|
31
|
-
|
32
|
-
|
31
|
+
collection.limit(limit).offset(offset)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
33
35
|
|
34
|
-
|
36
|
+
def define_header_pagination(limit, count, page)
|
37
|
+
response.headers['Pagination-Current-Page'] = page
|
35
38
|
response.headers['Pagination-Per'] = limit
|
36
39
|
response.headers['Pagination-Total-Pages'] = 1 + (count / limit).ceil
|
37
40
|
response.headers['Pagination-Total-Count'] = count
|
41
|
+
end
|
38
42
|
|
39
|
-
|
43
|
+
def extract_pagination_informations(collection)
|
44
|
+
limit = params[:page].fetch(
|
45
|
+
:size, FetcheableOnApi.configuration.pagination_default_size
|
46
|
+
).to_i
|
47
|
+
|
48
|
+
page = params[:page].fetch(:number, 1).to_i
|
49
|
+
offset = (page - 1) * limit
|
50
|
+
count = collection.except(:offset, :limit, :order).count
|
51
|
+
|
52
|
+
[limit, offset, count, page]
|
40
53
|
end
|
41
54
|
end
|
42
55
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module FetcheableOnApi
|
4
|
+
# Application of a sorting on a collection
|
4
5
|
module Sortable
|
5
6
|
#
|
6
7
|
# Supports
|
@@ -8,7 +9,7 @@ module FetcheableOnApi
|
|
8
9
|
SORT_ORDER = {
|
9
10
|
'+' => :asc,
|
10
11
|
'-' => :desc
|
11
|
-
}
|
12
|
+
}.freeze
|
12
13
|
|
13
14
|
#
|
14
15
|
# Public class methods
|
@@ -16,11 +17,12 @@ module FetcheableOnApi
|
|
16
17
|
def self.included(base)
|
17
18
|
base.class_eval do
|
18
19
|
extend ClassMethods
|
19
|
-
class_attribute :sorts_configuration, :
|
20
|
+
class_attribute :sorts_configuration, instance_writer: false
|
20
21
|
self.sorts_configuration = {}
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
25
|
+
# Detects url parameters and applies sorting
|
24
26
|
module ClassMethods
|
25
27
|
def sort_by(*attrs)
|
26
28
|
options = attrs.extract_options!
|
@@ -33,7 +35,7 @@ module FetcheableOnApi
|
|
33
35
|
as: attr
|
34
36
|
}
|
35
37
|
|
36
|
-
sorts_configuration[attr] =
|
38
|
+
sorts_configuration[attr] = sorts_configuration[attr].merge(options)
|
37
39
|
end
|
38
40
|
end
|
39
41
|
end
|
@@ -49,30 +51,57 @@ module FetcheableOnApi
|
|
49
51
|
|
50
52
|
def apply_sort(collection)
|
51
53
|
return collection if params[:sort].blank?
|
52
|
-
foa_valid_parameters!(:sort, foa_permitted_types: [String])
|
53
|
-
|
54
|
-
ordering = []
|
55
54
|
|
56
|
-
|
57
|
-
klass = sorts_configuration[attr.to_sym].fetch(:class_name, collection.klass)
|
58
|
-
field = sorts_configuration[attr.to_sym].fetch(:as, attr.to_sym).to_s
|
59
|
-
next unless klass.attribute_names.include?(field)
|
55
|
+
foa_valid_parameters!(:sort, foa_permitted_types: [String])
|
60
56
|
|
61
|
-
|
62
|
-
|
63
|
-
.arel_table[field]
|
64
|
-
.send(SORT_ORDER[sort_sign])
|
57
|
+
ordering = format_params(params[:sort]).map do |attr_name, sort_method|
|
58
|
+
arel_sort(attr_name, sort_method)
|
65
59
|
end
|
66
60
|
|
67
|
-
collection.order(ordering)
|
61
|
+
collection.order(ordering.compact)
|
68
62
|
end
|
69
63
|
|
70
64
|
private
|
71
65
|
|
72
|
-
def
|
66
|
+
def arel_sort(attr_name, sort_method)
|
67
|
+
return if sorts_configuration[attr_name].blank?
|
68
|
+
|
69
|
+
klass = class_for(attr_name, collection)
|
70
|
+
field = field_for(attr_name)
|
71
|
+
return unless belong_to_attributes_for?(klass, field)
|
72
|
+
|
73
|
+
klass.arel_table[field].send(sort_method)
|
74
|
+
end
|
75
|
+
|
76
|
+
def class_for(attr_name, collection)
|
77
|
+
sorts_configuration[attr_name].fetch(:class_name, collection.klass)
|
78
|
+
end
|
79
|
+
|
80
|
+
def field_for(attr_name)
|
81
|
+
sorts_configuration[attr_name].fetch(:as, attr_name).to_s
|
82
|
+
end
|
83
|
+
|
84
|
+
def belong_to_attributes_for?(klass, field)
|
85
|
+
klass.attribute_names.include?(field)
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# input: "-email,first_name"
|
90
|
+
# return: { email: :desc, first_name: :asc }
|
91
|
+
#
|
92
|
+
def format_params(params)
|
93
|
+
res = {}
|
94
|
+
|
73
95
|
params
|
74
96
|
.split(',')
|
75
|
-
.
|
97
|
+
.each do |attribute|
|
98
|
+
res[attribute.to_sym] = SORT_ORDER[sort_sign(attribute)]
|
99
|
+
end
|
100
|
+
res
|
101
|
+
end
|
102
|
+
|
103
|
+
def sort_sign(attribute)
|
104
|
+
attribute =~ /\A[+-]/ ? attribute.slice!(0) : '+'
|
76
105
|
end
|
77
106
|
end
|
78
107
|
end
|
@@ -1,11 +1,13 @@
|
|
1
1
|
module FetcheableOnApi
|
2
2
|
module Generators
|
3
|
+
# Create conf file
|
3
4
|
class InstallGenerator < Rails::Generators::Base
|
4
|
-
source_root File.expand_path(
|
5
|
-
desc
|
5
|
+
source_root File.expand_path('../templates', __dir__)
|
6
|
+
desc 'Creates FetcheableOnApi initializer for your application'
|
6
7
|
|
7
8
|
def copy_initializer
|
8
|
-
template
|
9
|
+
template 'fetcheable_on_api_initializer.rb',
|
10
|
+
'config/initializers/fetcheable_on_api.rb'
|
9
11
|
end
|
10
12
|
end
|
11
13
|
end
|
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.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Fabien
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-01-
|
11
|
+
date: 2019-01-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|