rice_cooker 0.1.3 → 0.1.4

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
  SHA1:
3
- metadata.gz: febeec59cbb060cf35bb66891a2c3017876f6263
4
- data.tar.gz: 80ec86ea16fc1496809c648789e937e7592332a0
3
+ metadata.gz: 929e7389b2a4a7d2d42b2c69cd6216886e6ddf3c
4
+ data.tar.gz: 4c58c53af490da81965005546c814966117b94ee
5
5
  SHA512:
6
- metadata.gz: 0b54debc8ddc3c88ae32a8b2eff042e4c1fa14bd73b6ccbc9ac32b0e2bb8c95b26db833186eadee1ac80639dad42037b69940cc4a7e4121f0c58272941525897
7
- data.tar.gz: 3d46fbf0f23c24ef4afa1c01e72612a843f8b28cdc10db72ac5d8d4d39558cb9f2b7c2090d0d1ca952e02b97adb37a28edfedf159fdc90b5247a9eb620b72e2a
6
+ metadata.gz: 3ecd88c5dd48b17eb2a34a4307e3380843c5c74e7781076c0e6f3120c3f192839e9c928b187ac1d4fc2e04bfd623ba37ad3b328efbf605bd97baa0ab73dc75cf
7
+ data.tar.gz: 21ee114e133548192359a057d939cacdc3189eed43e059e1803796785508a67f7743abdab0213a3f8ededf4acf4ab631c9d494d57e03d1d7df111c8453587d80
data/.rubocop.yml CHANGED
@@ -19,4 +19,7 @@ Metrics/PerceivedComplexity:
19
19
 
20
20
  Metrics/MethodLength:
21
21
  CountComments: false # count full line comments?
22
- Max: 20
22
+ Max: 40
23
+
24
+ Style/AsciiComments:
25
+ Enabled: false
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rice_cooker (0.1.3)
4
+ rice_cooker (0.1.4)
5
5
  actionpack (>= 4.2.0, < 5.1)
6
6
  has_scope (~> 0.7.0, >= 0.6.0)
7
7
  rails (>= 4.2.0, < 5.1)
@@ -47,7 +47,7 @@ GEM
47
47
  i18n (~> 0.7)
48
48
  minitest (~> 5.1)
49
49
  tzinfo (~> 1.1)
50
- arel (7.0.0)
50
+ arel (7.1.1)
51
51
  ast (2.3.0)
52
52
  builder (3.2.2)
53
53
  coderay (1.1.1)
@@ -63,7 +63,7 @@ GEM
63
63
  railties (>= 3.0.0)
64
64
  faker (1.6.1)
65
65
  i18n (~> 0.5)
66
- globalid (0.3.6)
66
+ globalid (0.3.7)
67
67
  activesupport (>= 4.1.0)
68
68
  has_scope (0.7.0)
69
69
  actionpack (>= 4.1, < 5.1)
@@ -163,7 +163,7 @@ GEM
163
163
  simplecov-html (~> 0.10.0)
164
164
  simplecov-html (0.10.0)
165
165
  slop (3.6.0)
166
- sprockets (3.6.3)
166
+ sprockets (3.7.0)
167
167
  concurrent-ruby (~> 1.0)
168
168
  rack (> 1, < 3)
169
169
  sprockets-rails (3.1.1)
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Rice Cooker
2
2
 
3
- [![Build Status](https://travis-ci.com/lambda2/rice_cooker.svg?token=zsj9q6JjpQd8brNcmt9S&branch=master)](https://travis-ci.com/lambda2/rice_cooker)
3
+ [![Build Status](https://travis-ci.org/lambda2/rice_cooker.svg?branch=master)](https://travis-ci.org/lambda2/rice_cooker)
4
4
 
5
5
  Handle sort, filters, searches, and ranges on Rails collections.
6
6
 
@@ -11,6 +11,7 @@ Handle sort, filters, searches, and ranges on Rails collections.
11
11
  In your `Gemfile`:
12
12
 
13
13
  ```ruby
14
+ gem 'has_scope'
14
15
  gem 'rice_cooker'
15
16
  ```
16
17
 
@@ -35,4 +36,28 @@ class UsersController < ActionController::Base
35
36
  end
36
37
  end
37
38
 
38
- ```
39
+ ```
40
+
41
+ ### Sorting
42
+
43
+ The `sort` parameter allow sorting on one or several, comma-separated, fields. The sort is applied in the order specified.
44
+ The sort order for each sort field is ascending unless it is prefixed with a minus (-), in which case it is descending.
45
+
46
+ For example, the `api.example.org/unicorns?sort=color,-name` url will return all the unicorns order by **color** in the ascending order, and if they have the same color, by **name**, on the descending order.
47
+
48
+ ### Filtering
49
+
50
+ The `filter` parameter is a hash allowing filtering for a field, as a key, on one or several, comma-separated, values. Only the fields matching the given filter(s) will be returned.
51
+
52
+ For example, the `api.example.org/unicorns?filter[color]=yellow&filter[age]=21,22` url will return all the **yellow** unicorns which are **21** OR **22** years old.
53
+
54
+ ### Ranging
55
+
56
+ The `range` parameter is a hash allowing filtering for a field, as a key, on two comma-separated bounds. Only the fields between the given bounds will be returned. The bounds are inclusives.
57
+
58
+ For example, the `api.example.org/unicorns?range[age]=21,42` url will return all the unicorns which are between **21** and **42** years old.
59
+
60
+
61
+ ## Specification
62
+
63
+ The gem try to follow the [RAPIS](https://github.com/lambda2/rapis) api convention.
@@ -0,0 +1,19 @@
1
+
2
+ module RiceCooker
3
+ class Base
4
+ include Helpers
5
+ attr_accessor :params
6
+ attr_accessor :model
7
+ attr_accessor :allowed_keys
8
+
9
+ def initialize(unformated_params, model)
10
+ @params = format_additional_param((unformated_params || {}), self.class.action)
11
+ @model = model
12
+ @allowed_keys = []
13
+ end
14
+
15
+ def self.action
16
+ raise "You must override action"
17
+ end
18
+ end
19
+ end
@@ -11,6 +11,12 @@ module RiceCooker
11
11
  class InvalidFilterValueException < Exception
12
12
  end
13
13
 
14
+ class InvalidSearchException < Exception
15
+ end
16
+
17
+ class InvalidSearchValueException < Exception
18
+ end
19
+
14
20
  class InvalidRangeException < Exception
15
21
  end
16
22
 
@@ -42,6 +48,15 @@ module RiceCooker
42
48
  end
43
49
  end
44
50
 
51
+ # Overridable method for available rangeable fields
52
+ def searchable_fields_for(model)
53
+ if model.respond_to?(:searchable_fields)
54
+ model.rangeable_fields.map(&:to_sym)
55
+ else
56
+ filterable_fields_for(model)
57
+ end
58
+ end
59
+
45
60
  # Overridable method for available rangeable fields
46
61
  def rangeable_fields_for(model)
47
62
  if model.respond_to?(:rangeable_fields)
@@ -180,6 +195,102 @@ module RiceCooker
180
195
  collection
181
196
  end
182
197
 
198
+
199
+ # ------------------------ Search helpers --------------------
200
+
201
+ # Va transformer le param url en hash exploitable
202
+ def parse_searching_param(searching_param, allowed_params)
203
+ return {} unless searching_param.present?
204
+
205
+ fields = {}
206
+
207
+ # Extract the fields for each type from the fields parameters
208
+ if searching_param.is_a?(Hash)
209
+ searching_param.each do |field, value|
210
+ resource_fields = value.split(',') unless value.nil? || value.empty?
211
+ fields[field.to_sym] = resource_fields
212
+ end
213
+ else
214
+ raise InvalidSearchException, "Invalid search format for #{searching_param}"
215
+ end
216
+ check_searching_param(fields, allowed_params)
217
+ fields
218
+ end
219
+
220
+ # Our little barrier <3
221
+ def check_searching_param(searching_param, allowed)
222
+ 🔞 = searching_param.keys.map(&:to_sym) - allowed.map(&:to_sym)
223
+ raise InvalidSearchException, "Attributes #{🔞.map(&:to_s).to_sentence} doesn't exists or aren't searchables. Available searchs are: #{allowed.to_sentence}" if 🔞.any?
224
+ end
225
+
226
+ # On va essayer de garder un format commun, qui est:
227
+ #
228
+ # ```
229
+ # search: {
230
+ # proc: -> (values) { * je fais des trucs avec les values * },
231
+ # all: ['les', 'valeurs', 'aceptées'],
232
+ # description: "La description dans la doc"
233
+ # }
234
+ # ```
235
+ #
236
+ # On va donc transformer `additional` dans le format ci-dessus
237
+ #
238
+ def format_additional_param(additional, context_format = 'searching')
239
+ if additional.is_a? Hash
240
+ additional = additional.map do |field, value|
241
+ if value.is_a?(Hash)
242
+ value = {
243
+ proc: nil,
244
+ all: [],
245
+ description: ''
246
+ }.merge(value)
247
+ elsif value.is_a? Array
248
+ value = {
249
+ proc: value.try(:at, 0),
250
+ all: value.try(:at, 1) || [],
251
+ description: value.try(:at, 2) || ''
252
+ }
253
+ elsif value.is_a? Proc
254
+ value = {
255
+ proc: value,
256
+ all: [],
257
+ description: ''
258
+ }
259
+ else
260
+ raise "Unable to format addional #{context_format} params (got #{additional})"
261
+ end
262
+ [field, value]
263
+ end.to_h
264
+ end
265
+ additional
266
+ end
267
+
268
+ def reduce_where(col, field, value)
269
+ reducer = nil
270
+ value.each do |v|
271
+ query = col.model.arel_table[field.to_sym].matches("%#{v.to_s}%")
272
+ reducer = (reducer ? reducer.or(query) : query)
273
+ end
274
+ col.where(reducer)
275
+ end
276
+
277
+ def apply_search_to_collection(col, searching_params, additional = {})
278
+ return col if col.nil?
279
+
280
+ searching_params.each do |field, value|
281
+ if additional.key?(field) && additional[field].key?(:proc)
282
+ col = col.instance_exec(value, &(additional[field][:proc]))
283
+ elsif value.is_a?(String)
284
+ col = col.where(col.model.arel_table[field.to_sym].matches("%#{value.join(' ')}%"))
285
+ elsif value.is_a?(Array)
286
+ col = reduce_where(col, field, value)
287
+ elsif value.is_a?(Hash) && value.key?(:proc)
288
+ col
289
+ end
290
+ end
291
+ col
292
+ end
293
+
183
294
  # ------------------------ Range helpers --------------------
184
295
 
185
296
  # Va transformer le param url en hash exploitable
@@ -6,52 +6,84 @@ module RiceCooker
6
6
 
7
7
  FILTER_PARAM = :filter
8
8
 
9
- module ClassMethods
10
- include Helpers
9
+ class Filter < RiceCooker::Base
10
+
11
+ def self.action
12
+ :filtering
13
+ end
14
+
15
+ def initialize(unformated_params, model)
16
+ super
17
+ @allowed_keys = (filterable_fields_for(@model) + @params.keys)
18
+ end
19
+
20
+ def register_bools
21
+ additional = (filterable_fields_for(@model) - [:created_at, :updated_at])
22
+ .select { |e| e =~ /_at$/ }
23
+ .select { |e| @params[e.to_s.gsub(/_at$/, '')].nil? }
24
+ additional.each { |fi| parse_bool(fi) }
25
+ end
26
+
27
+ def parse_bool(fi)
28
+ if fi.to_sym == :begin_at
29
+ parse_future_bool fi
30
+ else
31
+ parse_named_bool fi
32
+ end
33
+ end
34
+
35
+ def parse_future_bool(fi)
36
+ @params[:future] = {
37
+ proc: Filer.get_future_lambda("#{@model.quoted_table_name}.\"#{fi}\""),
38
+ all: %w(true false),
39
+ description: "Return only #{@model.to_s.underscore.humanize.downcase.pluralize} which begins in the future"
40
+ }
41
+ @allowed_keys << :future
42
+ end
43
+
44
+ def parse_named_bool(fi)
45
+ name = fi.to_s.gsub(/_at$/, '')
46
+ @params[name.to_sym] = {
47
+ proc: Filer.get_named_lambda(fi),
48
+ all: %w(true false),
49
+ description: "Return only #{name} #{@model.to_s.underscore.humanize.downcase.pluralize}"
50
+ }
51
+ @allowed_keys << name
52
+ end
53
+
54
+ def self.get_future_lambda(db_field)
55
+ lambda do |value|
56
+ value.first == 'true' ? where("#{db_field} >= ?", Time.zone.now) : where("#{db_field} < ?", Time.zone.now)
57
+ end
58
+ end
11
59
 
60
+ def self.get_named_lambda(fi)
61
+ lambda do |value|
62
+ value.first == 'true' ? where.not(fi => nil) : where(fi => nil)
63
+ end
64
+ end
65
+
66
+ def process(value, scope, custom, filter)
67
+ params = parse_filtering_param(value, filter)
68
+ apply_filter_to_collection(scope, params, custom)
69
+ end
70
+ end
71
+
72
+ module ClassMethods
12
73
  def filtered(additional_filtering_params = {})
13
74
  cattr_accessor :filtering_keys
14
75
  cattr_accessor :custom_filters
15
76
 
16
- # On normalize tout ca
17
- additional_filtering_params = format_additional_param(additional_filtering_params, 'filtering')
18
-
19
- # On recupere tous les filtres autorisés
20
- allowed_keys = (filterable_fields_for(resource_model) + additional_filtering_params.keys)
77
+ filter = Filter.new(additional_filtering_params, resource_model)
21
78
 
22
- # On fait une sorte de *register_bool_filter* sur tous les champs *_at
23
- additional = (filterable_fields_for(resource_model) - [:created_at, :updated_at])
24
- .select { |e| e =~ /_at$/ }
25
- .select { |e| additional_filtering_params[e.to_s.gsub(/_at$/, '')].nil? }
26
-
27
- additional.each do |fi|
28
- name = fi.to_s.gsub(/_at$/, '')
29
-
30
- if fi.to_sym == :begin_at
31
- db_field = "#{resource_model.quoted_table_name}.\"#{fi}\""
32
- additional_filtering_params[:future] = {
33
- proc: -> (value) { value.first == 'true' ? where("#{db_field} >= ?", Time.zone.now) : where("#{db_field} < ?", Time.zone.now) },
34
- all: %w(true false),
35
- description: "Return only #{resource_model.to_s.underscore.humanize.downcase.pluralize} which begins in the future"
36
- }
37
- allowed_keys << :future
38
- else
39
- additional_filtering_params[name.to_sym] = {
40
- proc: -> (value) { value.first == 'true' ? where.not(fi => nil) : where(fi => nil) },
41
- all: %w(true false),
42
- description: "Return only #{name} #{resource_model.to_s.underscore.humanize.downcase.pluralize}"
43
- }
44
- allowed_keys << name
45
- end
46
- end
79
+ filter.register_bools
47
80
 
48
81
  # On recupere le default
49
- self.filtering_keys = allowed_keys
50
- self.custom_filters = additional_filtering_params
82
+ self.filtering_keys = filter.allowed_keys
83
+ self.custom_filters = filter.params
51
84
 
52
- has_scope :filter, type: :hash, only: [:index] do |_controller, scope, value|
53
- params = parse_filtering_param(value, filtering_keys)
54
- scope = apply_filter_to_collection(scope, params, custom_filters)
85
+ has_scope FILTER_PARAM, type: :hash, only: [:index] do |_controller, scope, value|
86
+ scope = filter.process(value, scope, custom_filters, filtering_keys)
55
87
  scope
56
88
  end
57
89
  end
@@ -61,11 +93,11 @@ module RiceCooker
61
93
  #
62
94
  # name: le nom du filtre custom (ex: with_mark)
63
95
  # proc: le filtre, prend un arg `val` qui est un tableau des args du filtre
64
- # all: l'ensemble des valeurs acceptées pour le filtre. Laisser nil ou [] pour tout accepter
96
+ # all: l'ensemble des valeurs acceptées pour le filtre. Laisser nil ou pour tout accepter
65
97
  # description: La description dans la doc
66
98
  #
67
99
  def register_filter(name, proc, all = nil, description = nil)
68
- raise "A '#{name}' filter already exists for class #{self.class}" unless custom_filters[name].nil?
100
+ raise "A '#{name}' filter already exists for class #{self.class}" unless filter_exists?(name)
69
101
  custom_filters[name] = {
70
102
  proc: proc,
71
103
  all: all || [],
@@ -82,14 +114,19 @@ module RiceCooker
82
114
  # description: La description dans la doc
83
115
  #
84
116
  def register_bool_filter(name, field, description = nil)
85
- raise "A '#{name}' filter already exists for class #{self.class}" unless custom_filters[name].nil?
117
+ raise "A '#{name}' filter already exists for class #{self.class}" unless filter_exists?(name)
86
118
  custom_filters[name] = {
87
- proc: -> (value) { value.first == 'true' ? where.not(field => nil) : where(field => nil) },
119
+ proc: Filter.get_named_lambda(field),
88
120
  all: %w(true false),
89
121
  description: description || "Return only #{resource_model.to_s.underscore.humanize.downcase.pluralize} with a #{field}"
90
122
  }
91
123
  filtering_keys << name
92
124
  end
125
+
126
+ # Check if the given custom filter name already exists
127
+ def filter_exists?(name)
128
+ !custom_filters[name].nil?
129
+ end
93
130
  end
94
131
  end
95
132
  end
@@ -0,0 +1,51 @@
1
+ require 'active_support'
2
+
3
+ module RiceCooker
4
+ module Search
5
+ extend ActiveSupport::Concern
6
+
7
+ FILTER_PARAM = :search
8
+
9
+ module ClassMethods
10
+ include Helpers
11
+
12
+ def searched(additional_searching_params = {})
13
+ cattr_accessor :searching_keys
14
+ cattr_accessor :custom_searchs
15
+
16
+ # On normalize tout ca
17
+ additional_searching_params = format_additional_param(additional_searching_params, 'filtering')
18
+
19
+ # On recupere tous les filtres autorisés
20
+ allowed_keys = (searchable_fields_for(resource_model) + additional_searching_params.keys)
21
+
22
+ # On recupere le default
23
+ self.searching_keys = allowed_keys
24
+ self.custom_searchs = additional_searching_params
25
+
26
+ has_scope :search, type: :hash, only: [:index] do |_controller, scope, value|
27
+ params = parse_searching_param(value, searching_keys)
28
+ scope = apply_search_to_collection(scope, params, custom_searchs)
29
+ scope
30
+ end
31
+ end
32
+
33
+ # Ajoute un filtre custom
34
+ #
35
+ # name: le nom du filtre custom (ex: with_mark)
36
+ # proc: le filtre, prend un arg `val` qui est un tableau des args du filtre
37
+ # all: l'ensemble des valeurs acceptées pour le filtre. Laisser nil ou [] pour tout accepter
38
+ # description: La description dans la doc
39
+ #
40
+ def register_search(name, proc, all = nil, description = nil)
41
+ raise "A '#{name}' search already exists for class #{self.class}" unless custom_searchs[name].nil?
42
+ custom_searchs[name] = {
43
+ proc: proc,
44
+ all: all || [],
45
+ description: description || ''
46
+ }
47
+ searching_keys << name
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,3 +1,3 @@
1
1
  module RiceCooker
2
- VERSION = '0.1.3'.freeze
2
+ VERSION = '0.1.4'.freeze
3
3
  end
data/lib/rice_cooker.rb CHANGED
@@ -1,29 +1,32 @@
1
1
  require 'action_controller'
2
2
 
3
3
  module RiceCooker
4
- autoload :Helpers, 'rice_cooker/helpers'
4
+ autoload :Helpers, 'rice_cooker/base/helpers'
5
+ autoload :Base, 'rice_cooker/base/base'
5
6
  autoload :ClassMethods, 'rice_cooker/class_methods'
6
- autoload :Filter, 'rice_cooker/filter'
7
- autoload :Sort, 'rice_cooker/sort'
8
- autoload :Range, 'rice_cooker/range'
9
- autoload :VERSION, 'rice_cooker/version'
10
- end
7
+ autoload :Filter, 'rice_cooker/filter'
8
+ autoload :Sort, 'rice_cooker/sort'
9
+ autoload :Range, 'rice_cooker/range'
10
+ autoload :Search, 'rice_cooker/search'
11
+ autoload :VERSION, 'rice_cooker/version'
11
12
 
12
- module ActionController
13
- class Base
14
- def self.rice_cooked(base)
15
- base.class_eval do
16
- include RiceCooker::Sort
17
- include RiceCooker::Filter
18
- include RiceCooker::Range
19
- extend RiceCooker::ClassMethods
13
+ def self.rice_cooked(base)
14
+ base.class_eval do
15
+ include RiceCooker::Sort
16
+ include RiceCooker::Filter
17
+ include RiceCooker::Range
18
+ include RiceCooker::Search
19
+ extend RiceCooker::ClassMethods
20
20
 
21
- class_attribute :resource_model, instance_writer: false
21
+ class_attribute :resource_model, instance_writer: false
22
22
 
23
- protected :resource_model
24
- end
23
+ protected :resource_model
25
24
  end
25
+ end
26
+ end
26
27
 
27
- rice_cooked(self)
28
+ module ActionController
29
+ class Base
30
+ RiceCooker.rice_cooked(self)
28
31
  end
29
32
  end
@@ -49,6 +49,7 @@ class UsersController < ActionController::Base
49
49
  sorted
50
50
  filtered
51
51
  ranged
52
+ searched
52
53
 
53
54
  def index
54
55
  @users = apply_scopes(User).all
@@ -0,0 +1,267 @@
1
+ require 'pry'
2
+ require 'rice_cooker'
3
+ require 'active_record'
4
+ require 'spec_helper'
5
+
6
+ RSpec.describe RiceCooker::Search do
7
+ include RiceCooker::Helpers
8
+
9
+ # class User < ActiveRecord::Base; end
10
+
11
+ before do
12
+ @collection_class = User
13
+ @allowed_params = searchable_fields_for(@collection_class)
14
+ @collection = @collection_class.all
15
+
16
+ @test_search = {
17
+ with_the_letter: { proc: -> (value) { where('first_name ILIKE ?', value.map { |e| "%#{e}%" }) } },
18
+ without_the_letter: { proc: -> (value) { where.not('first_name ILIKE ?', value.map { |e| "%#{e}%" }) } }
19
+ }
20
+
21
+ @proc = -> (value) { value }
22
+ @all = -> (_value) { [1, 2, 3] }
23
+ end
24
+
25
+ describe 'Search params must be okay' do
26
+ it 'Null searching' do
27
+ # Default null searching
28
+ searching_params = parse_searching_param('', @allowed_params)
29
+ expect(searching_params).to be_eql({})
30
+ end
31
+
32
+ it 'Default searching' do
33
+ params = {
34
+ login: 'aaubin'
35
+ }
36
+
37
+ searching_params = parse_searching_param(params, @allowed_params)
38
+ expect(searching_params).to be_eql(login: ['aaubin'])
39
+ end
40
+
41
+ it 'Double searching' do
42
+ params = {
43
+ login: 'aaubin,qbollach'
44
+ }
45
+
46
+ searching_params = parse_searching_param(params, @allowed_params)
47
+ expect(searching_params).to be_eql(login: %w(aaubin qbollach))
48
+ end
49
+
50
+ it 'Multiple searching' do
51
+ params = {
52
+ login: 'aaubin,qbollach,andre',
53
+ id: '74,75,76'
54
+ }
55
+
56
+ searching_params = parse_searching_param(params, @allowed_params)
57
+ expect(searching_params).to be_eql(login: %w(aaubin qbollach andre),
58
+ id: %w(74 75 76))
59
+ end
60
+
61
+ it 'invalid args' do
62
+ # invalid args
63
+
64
+ params = {
65
+ wtf: 'aaubin,qbollach,andre',
66
+ id: '74,75,76'
67
+ }
68
+
69
+ expect { parse_searching_param(params, @allowed_params) }.to raise_error(RiceCooker::InvalidSearchException)
70
+ end
71
+ end
72
+
73
+ describe 'Must apply search to given collection' do
74
+ it 'Default null searching' do
75
+ searched_collection = apply_search_to_collection(@collection, {})
76
+ # puts searched_collection.to_sql
77
+ expect(searched_collection.to_sql).to match(/^((?!WHERE).)*$/)
78
+ end
79
+
80
+ it 'Default searching' do
81
+ searched_collection = apply_search_to_collection(@collection, login: ['aaubin'])
82
+ # puts searched_collection.to_sql
83
+ expect(searched_collection.to_sql).to match(/WHERE/)
84
+ expect(searched_collection.to_sql).to match(/\"login\" LIKE '%aaubin%'/)
85
+ end
86
+
87
+ it 'Double searching' do
88
+ # Desc searching
89
+ searched_collection = apply_search_to_collection(@collection, login: %w(aaubin qbollach))
90
+ # puts searched_collection.to_sql
91
+ expect(searched_collection.to_sql).to match(/WHERE/)
92
+ expect(searched_collection.to_sql).to match(/\"login\" LIKE '%aaubin%'/)
93
+ expect(searched_collection.to_sql).to match(/\"login\" LIKE '%qbollach%'/)
94
+ end
95
+
96
+ it 'Multiple searching' do
97
+ # Desc searching
98
+ searched_collection = apply_search_to_collection(@collection, login: %w(aaubin qbollach andre),
99
+ id: %w('74' '75' '76'))
100
+ # puts searched_collection.to_sql
101
+ expect(searched_collection.to_sql).to match(/\"login\" LIKE '%aaubin%'/)
102
+ expect(searched_collection.to_sql).to match(/\"login\" LIKE '%qbollach%'/)
103
+ expect(searched_collection.to_sql).to match(/\) AND \(/)
104
+ # puts searched_collection.to_sql
105
+ expect(searched_collection.to_sql).to match(/\"id\" LIKE 0/)
106
+ expect(searched_collection.to_sql).to match(/\"id\" LIKE 0/)
107
+ expect(searched_collection.to_sql).to match(/\) AND \(/)
108
+ end
109
+ end
110
+
111
+ describe 'Must apply custom searchs to given collection' do
112
+ it 'Default null searching' do
113
+ searched_collection = apply_search_to_collection(@collection, {}, @test_search)
114
+ # puts searched_collection.to_sql
115
+ expect(searched_collection.to_sql).to match(/^((?!WHERE).)*$/)
116
+ end
117
+
118
+ it 'Default searching' do
119
+ searched_collection = apply_search_to_collection(@collection, { with_the_letter: ['a'] }, @test_search)
120
+ # puts searched_collection.to_sql
121
+ expect(searched_collection.to_sql).to match(/WHERE/)
122
+ expect(searched_collection.to_sql).to match(/ILIKE/)
123
+ end
124
+
125
+ it 'Double searching' do
126
+ # Desc searching
127
+ searched_collection = apply_search_to_collection(@collection, { with_the_letter: ['a'], without_the_letter: ['l'] }, @test_search)
128
+ # puts searched_collection.to_sql
129
+ expect(searched_collection.to_sql).to match(/WHERE/)
130
+ expect(searched_collection.to_sql).to match(/first_name ILIKE '%a%'/)
131
+ expect(searched_collection.to_sql).to match(/NOT \(first_name ILIKE '%l%'\)/)
132
+ end
133
+
134
+ # it 'invalid args' do
135
+ # # invalid args
136
+ # expect do
137
+ # apply_search_to_collection(
138
+ # @collection,
139
+ # { sorted: %w(true baguette) },
140
+ # format_additional_param(sorted: [-> (v) { v }, %w(true false maybe)])
141
+ # )
142
+ # end.to raise_error(RiceCooker::InvalidSearchValueException)
143
+ # end
144
+ end
145
+
146
+ describe 'Additional params must be correctly formated' do
147
+ it 'No additional params' do
148
+ formated = format_additional_param({})
149
+ expect(formated).to be_eql({})
150
+ end
151
+
152
+ it 'Already correctly formatted additional params' do
153
+ p = { search: {
154
+ proc: @proc,
155
+ all: [1, 2, 3],
156
+ description: 'A good search'
157
+ } }
158
+ formated = format_additional_param(p)
159
+ expect(formated).to be_eql(p)
160
+ end
161
+
162
+ it 'Missing description additional params' do
163
+ p = { search: {
164
+ proc: @proc,
165
+ all: [1, 2, 3]
166
+ } }
167
+ expected = { search: {
168
+ proc: @proc,
169
+ all: [1, 2, 3],
170
+ description: ''
171
+ } }
172
+ formated = format_additional_param(p)
173
+ expect(formated).to be_eql(expected)
174
+ end
175
+
176
+ it 'Only proc additional params' do
177
+ p = { search: @proc }
178
+ expected = { search: {
179
+ proc: @proc,
180
+ all: [],
181
+ description: ''
182
+ } }
183
+ formated = format_additional_param(p)
184
+ expect(formated).to be_eql(expected)
185
+ end
186
+
187
+ it 'Array with proc and all additional params' do
188
+ p = { search: [@proc, @all] }
189
+ expected = { search: {
190
+ proc: @proc,
191
+ all: @all,
192
+ description: ''
193
+ } }
194
+ formated = format_additional_param(p)
195
+ expect(formated).to be_eql(expected)
196
+ end
197
+
198
+ it 'Multiple, std + Array with proc and all additional params' do
199
+ p = {
200
+ tata: @proc,
201
+ toto: { proc: @proc, all: [1, 2] },
202
+ search: [@proc, @all],
203
+ tutu: { proc: @proc, description: 'Buuuuh' }
204
+ }
205
+ expected = {
206
+ tata: {
207
+ proc: @proc,
208
+ all: [],
209
+ description: ''
210
+ },
211
+ toto: {
212
+ proc: @proc,
213
+ all: [1, 2],
214
+ description: ''
215
+ },
216
+ search: {
217
+ proc: @proc,
218
+ all: @all,
219
+ description: ''
220
+ },
221
+ tutu: {
222
+ proc: @proc,
223
+ all: [],
224
+ description: 'Buuuuh'
225
+ }
226
+ }
227
+ formated = format_additional_param(p)
228
+ expect(formated).to be_eql(expected)
229
+ end
230
+ end
231
+ end
232
+
233
+ RSpec.describe UsersController, type: :controller do
234
+ include RiceCooker::Helpers
235
+
236
+ before { request.host = 'example.org' }
237
+
238
+ describe 'GET #index' do
239
+ it 'without search parameter' do
240
+ process :index, method: :get, params: { search: '', format: :json }
241
+ expect(response.body).to eq(User.all.order(id: :desc).to_json)
242
+ end
243
+
244
+ it 'with simple search parameter' do
245
+ process :index, method: :get, params: { search: { login: 'a' }, format: :json }
246
+ expect(response.body).to eq(User.where(User.arel_table[:login].matches("%a%")).order(id: :desc).to_json)
247
+ end
248
+
249
+ it 'with double search parameter' do
250
+ process :index, method: :get, params: { search: { login: 'a,q' }, format: :json }
251
+ expect(response.body).to eq(User.where(
252
+ User.arel_table[:login].matches("%a%").or(User.arel_table[:login].matches("%q%"))
253
+ ).order(id: :desc).to_json)
254
+ end
255
+
256
+ it 'with double and multiple search parameter' do
257
+ process :index, method: :get, params: { search: { login: 'a,q', email: 't' }, format: :json }
258
+ expect(response.body).to eq(
259
+ User.where(
260
+ User.arel_table[:login].matches("%a%").or(User.arel_table[:login].matches("%q%"))
261
+ ).where(
262
+ User.arel_table[:email].matches("%t%")
263
+ ).order(id: :desc).to_json
264
+ )
265
+ end
266
+ end
267
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rice_cooker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andre Aubin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-06 00:00:00.000000000 Z
11
+ date: 2016-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -288,10 +288,12 @@ files:
288
288
  - README.md
289
289
  - Rakefile
290
290
  - lib/rice_cooker.rb
291
+ - lib/rice_cooker/base/base.rb
292
+ - lib/rice_cooker/base/helpers.rb
291
293
  - lib/rice_cooker/class_methods.rb
292
294
  - lib/rice_cooker/filter.rb
293
- - lib/rice_cooker/helpers.rb
294
295
  - lib/rice_cooker/range.rb
296
+ - lib/rice_cooker/search.rb
295
297
  - lib/rice_cooker/sort.rb
296
298
  - lib/rice_cooker/version.rb
297
299
  - lib/tasks/rice_cooker_tasks.rake
@@ -302,6 +304,7 @@ files:
302
304
  - spec/mocks/mocks.rb
303
305
  - spec/mocks/models/user.rb
304
306
  - spec/range/range_spec.rb
307
+ - spec/search/search_spec.rb
305
308
  - spec/sort/sort_spec.rb
306
309
  - spec/spec_helper.rb
307
310
  homepage: https://github.com/lambda2/rice_cooker
@@ -335,5 +338,6 @@ test_files:
335
338
  - spec/mocks/mocks.rb
336
339
  - spec/mocks/models/user.rb
337
340
  - spec/range/range_spec.rb
341
+ - spec/search/search_spec.rb
338
342
  - spec/sort/sort_spec.rb
339
343
  - spec/spec_helper.rb