fetcheable_on_api 0.2.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6f6a27e74d75387bd75d978d8992ef31d2338c4b94685323f006efc42161400
4
- data.tar.gz: 506acb6f864eec138f3f91ce4715de0b86caaf603cffd3ec32d822888e1002df
3
+ metadata.gz: 936c435eaac09ab3f50c5fe2fc650df5f857f99077a3a7cd24c9a7cbfa16bbeb
4
+ data.tar.gz: 5f029dd6d1fe43c945f0740b947ce0242444a6d6b21a556bc93c6c43295f02fd
5
5
  SHA512:
6
- metadata.gz: bea41ab188e81170e282027ed91c0fec55dc13e0c94b8ebd59cdbd819a00eb0809454d1f4660520077f3fb70614d837ac6a842e5084d7fd78145d23b05d47425
7
- data.tar.gz: deeaaba6a658b4ec43ebbc29a4478166212576997a293d131992ecb3937e7dd4dc36e477ba5f54fcbc36a6fa127f5df1f98deff0a090cb00d75a308ce6dc165a
6
+ metadata.gz: 5e11946ac5da7685b8a10e5638fd05c9dd5d55f5cf95309ccd2398e4b597ccd324ce68b3bdb307b8ed305500797f8a758af2c1b805ffdd671e7ad3ca407810bc
7
+ data.tar.gz: f60417bd41596796aba7191c24a5dcd2b077945b50ae11dccee646a8d0c6feb539b7b8aa5198e96b83b6eb19c0079f36f1ffefd28d83baadbeb21b37adeb2156
@@ -1,13 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fetcheable_on_api (0.1.4)
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.1)
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.16.6
66
+ 1.17.1
@@ -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
- def foa_valid_parameters!(*keys, foa_permitted_types: foa_default_permitted_types)
61
- raise FetcheableOnApi::ArgumentError.new(
62
- "Incorrect type #{params.dig(*keys).class} for params #{keys}"
63
- ) unless foa_valid_params_types(*keys, foa_permitted_types: foa_permitted_types)
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(*keys, foa_permitted_types: foa_default_permitted_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,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FetcheableOnApi
4
+ # Default configuration
4
5
  class Configuration
5
6
  attr_accessor :pagination_default_size
6
7
 
@@ -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
- :does_not_match_all,
10
- :does_not_match_any,
11
- :eq_all,
12
- :eq_any,
13
- :gt_all,
14
- :gt_any,
15
- :gteq_all,
16
- :gteq_any,
17
- :in_all,
18
- :in_any,
19
- :lt_all,
20
- :lt_any,
21
- :lteq_all,
22
- :lteq_any,
23
- :matches_all,
24
- :matches_any,
25
- :not_eq_all,
26
- :not_eq_any,
27
- :not_in_all,
28
- :not_in_any
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] = filters_configuration[attr].merge(options)
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 try_parse_array(values, format)
70
- # array = JSON.parse(values)
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) || PREDICATES_WITH_ARRAY.exclude?(predicate.to_sym)
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
- raise ArgumentError, "unsupported predicate `#{predicate}`" unless predicate.respond_to?(:call)
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 = params[:page].fetch(
27
- :size,
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
- offset = (params[:page].fetch(:number, 1).to_i - 1) * limit
32
- count = collection.except(:offset, :limit, :order).count
31
+ collection.limit(limit).offset(offset)
32
+ end
33
+
34
+ private
33
35
 
34
- response.headers['Pagination-Current-Page'] = params[:page].fetch(:number, 1)
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
- collection.limit(limit).offset(offset)
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, :instance_writer => false
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] = self.sorts_configuration[attr].merge(options)
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
- clean_params(params[:sort]).each do |attr|
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
- sort_sign = (attr =~ /\A[+-]/) ? attr.slice!(0) : '+'
62
- ordering << klass
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 clean_params(params)
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
- .select { |e| sorts_configuration.keys.map(&:to_s).include?(e) }
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,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FetcheableOnApi
4
- VERSION = '0.2.1'.freeze
4
+ VERSION = '0.2.2'.freeze
5
5
  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("../../templates", __FILE__)
5
- desc "Creates FetcheableOnApi initializer for your application"
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 "fetcheable_on_api_initializer.rb", "config/initializers/fetcheable_on_api.rb"
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.1
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-08 00:00:00.000000000 Z
11
+ date: 2019-01-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport