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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 526e0f21adee665430b54272194fb618c1fb6de3b1ddd8304c1e503ea7038d12
4
- data.tar.gz: 36475791e8222dca95087228f221e2a90151fe8e0da0d3380445cd5892409a3e
3
+ metadata.gz: d557420a13e2668f210f92dd25263770b8345dc82b1cc68ec49171028a708b0c
4
+ data.tar.gz: e4c9edbd515862b132c67d6a8cefd4a2e3d76283d931ffa7d9f43b436f271ef3
5
5
  SHA512:
6
- metadata.gz: 7fea1bcc86e766c6bf3047b3b0bf7c1657abcd8d03df98e1a8c1eff279a6acf8b5c40859edb66e48e530a835a769a6a8b33c01a5d98d1cedc9e31d1df0d861c5
7
- data.tar.gz: 21ef10f2fa41413ccc14ed66e511b38f5d6e8268de989423640b4ea486859af1c6247641f86505ceaf95fb39fa639903ffe57d06fea65c8ef0ad7cbe93050242
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 two kind of predicates are supported:
436
+ Currently 33 predicates are supported ([more details here](https://github.com/fabienpiette/fetcheable_on_api/wiki/Predicates)):
437
437
 
438
- + `:ilike` which is the default behaviour and will match the parameter with the SQL fragment `ILIKE '%foo%'`.
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
 
@@ -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 = Class.new(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 valid_parameters!(*keys, permitted_types: default_permitted_types)
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 valid_params_types(*keys, permitted_types: permitted_types)
63
+ ) unless foa_valid_params_types(*keys, foa_permitted_types: foa_permitted_types)
62
64
  end
63
65
 
64
- def valid_params_types(*keys, permitted_types: default_permitted_types)
65
- permitted_types.inject(false) do |res, type|
66
- res || valid_params_type(params.dig(*keys), type)
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 valid_params_type(value, type)
72
+ def foa_valid_params_type(value, type)
71
73
  value.is_a?(type)
72
74
  end
73
75
 
74
- def default_permitted_types
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, :instance_writer => false
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
- valid_parameters!(:filter)
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
- values.split(',').map do |value|
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 :ilike
63
- klass.arel_table[column_name].matches("%#{value}%")
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
@@ -21,7 +21,7 @@ module FetcheableOnApi
21
21
 
22
22
  def apply_pagination(collection)
23
23
  return collection if params[:page].blank?
24
- valid_parameters!(:page)
24
+ foa_valid_parameters!(:page)
25
25
 
26
26
  limit = params[:page].fetch(
27
27
  :size,
@@ -49,7 +49,7 @@ module FetcheableOnApi
49
49
 
50
50
  def apply_sort(collection)
51
51
  return collection if params[:sort].blank?
52
- valid_parameters!(:sort, permitted_types: [String])
52
+ foa_valid_parameters!(:sort, permitted_types: [String])
53
53
 
54
54
  ordering = {}
55
55
  sorted_params = params[:sort].split(',')
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FetcheableOnApi
4
- VERSION = '0.1.7'.freeze
4
+ VERSION = '0.1.8'.freeze
5
5
  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.1.7
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-03 00:00:00.000000000 Z
11
+ date: 2018-12-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport