rice_cooker 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,264 @@
1
+ require 'pry'
2
+ require 'rice_cooker'
3
+ require 'active_record'
4
+ require 'spec_helper'
5
+
6
+ RSpec.describe RiceCooker::Filter do
7
+ include RiceCooker::Helpers
8
+
9
+ # class User < ActiveRecord::Base; end
10
+
11
+ before do
12
+ @collection_class = User
13
+ @allowed_params = filterable_fields_for(@collection_class)
14
+ @collection = @collection_class.all
15
+
16
+ @test_filter = {
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 'Filter params must be okay' do
26
+ it 'Null filtering' do
27
+ # Default null filtering
28
+ filtering_params = parse_filtering_param('', @allowed_params)
29
+ expect(filtering_params).to be_eql({})
30
+ end
31
+
32
+ it 'Default filtering' do
33
+ params = {
34
+ login: 'aaubin'
35
+ }
36
+
37
+ filtering_params = parse_filtering_param(params, @allowed_params)
38
+ expect(filtering_params).to be_eql(login: ['aaubin'])
39
+ end
40
+
41
+ it 'Double filtering' do
42
+ params = {
43
+ login: 'aaubin,qbollach'
44
+ }
45
+
46
+ filtering_params = parse_filtering_param(params, @allowed_params)
47
+ expect(filtering_params).to be_eql(login: %w(aaubin qbollach))
48
+ end
49
+
50
+ it 'Multiple filtering' do
51
+ params = {
52
+ login: 'aaubin,qbollach,andre',
53
+ id: '74,75,76'
54
+ }
55
+
56
+ filtering_params = parse_filtering_param(params, @allowed_params)
57
+ expect(filtering_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_filtering_param(params, @allowed_params) }.to raise_error(RiceCooker::InvalidFilterException)
70
+ end
71
+ end
72
+
73
+ describe 'Must apply filter to given collection' do
74
+ it 'Default null filtering' do
75
+ filtered_collection = apply_filter_to_collection(@collection, {})
76
+ # puts filtered_collection.to_sql
77
+ expect(filtered_collection.to_sql).to match(/^((?!WHERE).)*$/)
78
+ end
79
+
80
+ it 'Default filtering' do
81
+ filtered_collection = apply_filter_to_collection(@collection, login: ['aaubin'])
82
+ # puts filtered_collection.to_sql
83
+ expect(filtered_collection.to_sql).to match(/WHERE/)
84
+ expect(filtered_collection.to_sql).to match(/"login" = 'aaubin'/)
85
+ end
86
+
87
+ it 'Double filtering' do
88
+ # Desc filtering
89
+ filtered_collection = apply_filter_to_collection(@collection, login: %w(aaubin qbollach))
90
+ # puts filtered_collection.to_sql
91
+ expect(filtered_collection.to_sql).to match(/WHERE/)
92
+ expect(filtered_collection.to_sql).to match(/"login" IN \('aaubin', 'qbollach'\)/)
93
+ end
94
+
95
+ it 'Multiple filtering' do
96
+ # Desc filtering
97
+ filtered_collection = apply_filter_to_collection(@collection, login: %w(aaubin qbollach andre),
98
+ id: %w(74 75 76))
99
+ # puts filtered_collection.to_sql
100
+ expect(filtered_collection.to_sql).to match(/WHERE/)
101
+ expect(filtered_collection.to_sql).to match(/"login" IN \('aaubin', 'qbollach', 'andre'\)/)
102
+ expect(filtered_collection.to_sql).to match(/AND/)
103
+ end
104
+ end
105
+
106
+ describe 'Must apply custom filters to given collection' do
107
+ it 'Default null filtering' do
108
+ filtered_collection = apply_filter_to_collection(@collection, {}, @test_filter)
109
+ # puts filtered_collection.to_sql
110
+ expect(filtered_collection.to_sql).to match(/^((?!WHERE).)*$/)
111
+ end
112
+
113
+ it 'Default filtering' do
114
+ filtered_collection = apply_filter_to_collection(@collection, { with_the_letter: ['a'] }, @test_filter)
115
+ # puts filtered_collection.to_sql
116
+ expect(filtered_collection.to_sql).to match(/WHERE/)
117
+ expect(filtered_collection.to_sql).to match(/ILIKE/)
118
+ end
119
+
120
+ it 'Double filtering' do
121
+ # Desc filtering
122
+ filtered_collection = apply_filter_to_collection(@collection, { with_the_letter: ['a'], without_the_letter: ['l'] }, @test_filter)
123
+ # puts filtered_collection.to_sql
124
+ expect(filtered_collection.to_sql).to match(/WHERE/)
125
+ expect(filtered_collection.to_sql).to match(/first_name ILIKE '%a%'/)
126
+ expect(filtered_collection.to_sql).to match(/NOT \(first_name ILIKE '%l%'\)/)
127
+ end
128
+
129
+ it 'Multiple filtering' do
130
+ # Desc filtering
131
+ filtered_collection = apply_filter_to_collection(@collection, login: %w(aaubin qbollach andre),
132
+ id: %w(74 75 76))
133
+ # puts filtered_collection.to_sql
134
+ expect(filtered_collection.to_sql).to match(/WHERE/)
135
+ expect(filtered_collection.to_sql).to match(/"login" IN \('aaubin', 'qbollach', 'andre'\)/)
136
+ expect(filtered_collection.to_sql).to match(/AND/)
137
+ end
138
+
139
+ it 'invalid args' do
140
+ # invalid args
141
+ expect do
142
+ apply_filter_to_collection(
143
+ @collection,
144
+ { sorted: %w(true baguette) },
145
+ format_additional_param(sorted: [-> (v) { v }, %w(true false maybe)])
146
+ )
147
+ end.to raise_error(RiceCooker::InvalidFilterValueException)
148
+ end
149
+ end
150
+
151
+ describe 'Additional params must be correctly formated' do
152
+ it 'No additional params' do
153
+ formated = format_additional_param({})
154
+ expect(formated).to be_eql({})
155
+ end
156
+
157
+ it 'Already correctly formatted additional params' do
158
+ p = { filter: {
159
+ proc: @proc,
160
+ all: [1, 2, 3],
161
+ description: 'A good filter'
162
+ } }
163
+ formated = format_additional_param(p)
164
+ expect(formated).to be_eql(p)
165
+ end
166
+
167
+ it 'Missing description additional params' do
168
+ p = { filter: {
169
+ proc: @proc,
170
+ all: [1, 2, 3]
171
+ } }
172
+ expected = { filter: {
173
+ proc: @proc,
174
+ all: [1, 2, 3],
175
+ description: ''
176
+ } }
177
+ formated = format_additional_param(p)
178
+ expect(formated).to be_eql(expected)
179
+ end
180
+
181
+ it 'Only proc additional params' do
182
+ p = { filter: @proc }
183
+ expected = { filter: {
184
+ proc: @proc,
185
+ all: [],
186
+ description: ''
187
+ } }
188
+ formated = format_additional_param(p)
189
+ expect(formated).to be_eql(expected)
190
+ end
191
+
192
+ it 'Array with proc and all additional params' do
193
+ p = { filter: [@proc, @all] }
194
+ expected = { filter: {
195
+ proc: @proc,
196
+ all: @all,
197
+ description: ''
198
+ } }
199
+ formated = format_additional_param(p)
200
+ expect(formated).to be_eql(expected)
201
+ end
202
+
203
+ it 'Multiple, std + Array with proc and all additional params' do
204
+ p = {
205
+ tata: @proc,
206
+ toto: { proc: @proc, all: [1, 2] },
207
+ filter: [@proc, @all],
208
+ tutu: { proc: @proc, description: 'Buuuuh' }
209
+ }
210
+ expected = {
211
+ tata: {
212
+ proc: @proc,
213
+ all: [],
214
+ description: ''
215
+ },
216
+ toto: {
217
+ proc: @proc,
218
+ all: [1, 2],
219
+ description: ''
220
+ },
221
+ filter: {
222
+ proc: @proc,
223
+ all: @all,
224
+ description: ''
225
+ },
226
+ tutu: {
227
+ proc: @proc,
228
+ all: [],
229
+ description: 'Buuuuh'
230
+ }
231
+ }
232
+ formated = format_additional_param(p)
233
+ expect(formated).to be_eql(expected)
234
+ end
235
+ end
236
+ end
237
+
238
+ RSpec.describe UsersController, type: :controller do
239
+ include RiceCooker::Helpers
240
+
241
+ before { request.host = 'example.org' }
242
+
243
+ describe 'GET #index' do
244
+ it 'without filter parameter' do
245
+ process :index, method: :get, params: { filter: '', format: :json }
246
+ expect(response.body).to eq(User.all.order(id: :desc).to_json)
247
+ end
248
+
249
+ it 'with simple filter parameter' do
250
+ process :index, method: :get, params: { filter: { login: 'aaubin' }, format: :json }
251
+ expect(response.body).to eq(User.where(login: 'aaubin').order(id: :desc).to_json)
252
+ end
253
+
254
+ it 'with double filter parameter' do
255
+ process :index, method: :get, params: { filter: { login: 'aaubin,qbollach' }, format: :json }
256
+ expect(response.body).to eq(User.where(login: %w(aaubin qbollach)).order(id: :desc).to_json)
257
+ end
258
+
259
+ it 'with double and multiple filter parameter' do
260
+ process :index, method: :get, params: { filter: { login: 'aaubin,qbollach', email: 'tata' }, format: :json }
261
+ expect(response.body).to eq(User.where(login: %w(aaubin qbollach), email: 'tata').order(id: :desc).to_json)
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,2 @@
1
+ ActiveRecord::Base.configurations = { 'test' => { adapter: 'sqlite3', database: ':memory:' } }
2
+ ActiveRecord::Base.establish_connection :test
@@ -0,0 +1,58 @@
1
+ require 'action_controller'
2
+ require 'ostruct'
3
+ require 'has_scope'
4
+
5
+ module Rails
6
+ def self.application
7
+ @application ||= begin
8
+ routes = ActionDispatch::Routing::RouteSet.new
9
+ OpenStruct.new(routes: routes, env_config: {})
10
+ end
11
+ end
12
+ end
13
+
14
+ module ControllerExampleGroup
15
+ def self.included(base)
16
+ base.extend ClassMethods
17
+ base.send(:include, ActionController::TestCase::Behavior)
18
+
19
+ base.prepend_before do
20
+ @routes = Rails.application.routes
21
+ @controller = described_class.new
22
+ end
23
+ end
24
+
25
+ module ClassMethods
26
+ def setup(*methods)
27
+ methods.each do |method|
28
+ if method.to_s =~ /^setup_(fixtures|controller_request_and_response)$/
29
+ prepend_before { send method }
30
+ else
31
+ before { send method }
32
+ end
33
+ end
34
+ end
35
+
36
+ def teardown(*methods)
37
+ methods.each { |method| after { send method } }
38
+ end
39
+ end
40
+ end
41
+
42
+ Rails.application.routes.draw do
43
+ resources :users, only: [:index]
44
+ end
45
+
46
+ class UsersController < ActionController::Base
47
+ include Rails.application.routes.url_helpers
48
+
49
+ sorted
50
+ filtered
51
+ ranged
52
+
53
+ def index
54
+ @users = apply_scopes(User).all
55
+ # p @users.to_sql
56
+ render json: @users
57
+ end
58
+ end
@@ -0,0 +1,4 @@
1
+
2
+ require File.join(File.dirname(__FILE__), *%w(config))
3
+ require File.join(File.dirname(__FILE__), *%w(models user))
4
+ require File.join(File.dirname(__FILE__), *%w(controllers users_controller))
@@ -0,0 +1,31 @@
1
+ class User < ActiveRecord::Base
2
+ has_many :comments
3
+
4
+ def self.rangeable_fields
5
+ [:id, :login, :email]
6
+ end
7
+ end
8
+
9
+ class Comment < ActiveRecord::Base
10
+ belongs_to :user
11
+ end
12
+
13
+ # migrations
14
+ class CreateAllTables < ActiveRecord::Migration
15
+ def self.up
16
+ create_table(:users) { |t| t.string :login; t.string :email; t.timestamps }
17
+ create_table(:comments) { |t| t.integer :user_id; t.string :content; t.timestamps }
18
+ end
19
+ end
20
+
21
+ ActiveRecord::Migration.verbose = true
22
+ CreateAllTables.up
23
+
24
+ [
25
+ { login: 'andre', email: 'tata' },
26
+ { login: 'mathieu', email: 'toto' },
27
+ { login: 'bobol', email: 'titi' },
28
+ { login: 'fred', email: 'gratti' },
29
+ { login: 'jeanne', email: 'zapata' },
30
+ { login: 'angie', email: 'tutu' }
31
+ ].each { |u| User.create!(u) }
@@ -0,0 +1,265 @@
1
+ require 'rice_cooker'
2
+ require 'active_record'
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe RiceCooker::Range do
6
+ include RiceCooker::Helpers
7
+
8
+ before do
9
+ @collection_class = User
10
+ @allowed_params = rangeable_fields_for(@collection_class)
11
+ @collection = @collection_class.all
12
+
13
+ @test_range = {
14
+ between_the_letter: { proc: -> (start_field, end_field) { where('first_name BETWEEN ? AND ?', start_field, end_field) } },
15
+ not_between_the_letter: { proc: -> (start_field, end_field) { where('first_name NOT BETWEEN ? AND ?', start_field, end_field) } }
16
+ }
17
+
18
+ @proc = -> (value) { value }
19
+ @all = -> (_value) { [1, 2, 3] }
20
+ end
21
+
22
+ describe 'Range params must be okay' do
23
+ it 'Null ranged' do
24
+ # Default null ranged
25
+ ranged_params = parse_ranged_param('', @allowed_params)
26
+ expect(ranged_params).to be_eql({})
27
+ end
28
+
29
+ it 'Default ranged' do
30
+ params = {
31
+ id: '1,5'
32
+ }
33
+ ranged_params = parse_ranged_param(params, @allowed_params)
34
+ expect(ranged_params).to be_eql(id: %w(1 5))
35
+ end
36
+
37
+ it 'Multiple ranged' do
38
+ params = {
39
+ login: 'aaubin,bobol',
40
+ id: '4,5'
41
+ }
42
+ ranged_params = parse_ranged_param(params, @allowed_params)
43
+ expect(ranged_params).to be_eql(login: %w(aaubin bobol),
44
+ id: %w(4 5))
45
+ end
46
+
47
+ it 'too many args' do
48
+ # invalid args
49
+
50
+ params = {
51
+ wtf: 'aaubin,qbollach,andre',
52
+ id: '74,75,76'
53
+ }
54
+
55
+ expect { parse_ranged_param(params, @allowed_params) }.to raise_error(RiceCooker::InvalidRangeException)
56
+ end
57
+
58
+ it 'too few args' do
59
+ # invalid args
60
+
61
+ params = {
62
+ wtf: 'aaubin',
63
+ id: '74'
64
+ }
65
+
66
+ expect { parse_ranged_param(params, @allowed_params) }.to raise_error(RiceCooker::InvalidRangeException)
67
+ end
68
+ end
69
+
70
+ describe 'Must apply range to given collection' do
71
+ it 'Default null ranged' do
72
+ ranged_collection = apply_range_to_collection(@collection, {})
73
+ expect(ranged_collection.to_sql).to match(/^((?!WHERE).)*$/)
74
+ end
75
+
76
+ it 'Default ranged' do
77
+ ranged_collection = apply_range_to_collection(@collection, login: %w(aaubin qbollach))
78
+ expect(ranged_collection.to_sql).to match(/BETWEEN/)
79
+ expect(ranged_collection.to_sql).to match(/'aaubin'/)
80
+ end
81
+
82
+ it 'Double ranged' do
83
+ # Desc ranged
84
+ ranged_collection = apply_range_to_collection(@collection, login: %w(aaubin qbollach), id: [1, 2])
85
+ expect(ranged_collection.to_sql).to match(/BETWEEN/)
86
+ expect(ranged_collection.to_sql).to match(/'aaubin'/)
87
+ end
88
+
89
+ it 'Invalid ranged' do
90
+ # Desc ranged
91
+ expect do
92
+ apply_range_to_collection(@collection, login: %w(aaubin qbollach andre),
93
+ id: ['74'])
94
+ end.to raise_error(RiceCooker::InvalidRangeValueException)
95
+ end
96
+ end
97
+
98
+ describe 'Must apply custom ranges to given collection' do
99
+
100
+ it 'Default null ranged' do
101
+ ranged_collection = apply_range_to_collection(@collection, {}, @test_range)
102
+ expect(ranged_collection.to_sql).to match(/^((?!WHERE).)*$/)
103
+ end
104
+
105
+ it 'Default ranged' do
106
+ ranged_collection = apply_range_to_collection(@collection, {between_the_letter: ['a', 'b']}, @test_range)
107
+ expect(ranged_collection.to_sql).to match(/WHERE/)
108
+ expect(ranged_collection.to_sql).to match(/BETWEEN/)
109
+ end
110
+
111
+ it 'Double ranged' do
112
+ # Desc ranged
113
+ ranged_collection = apply_range_to_collection(@collection, {
114
+ between_the_letter: ['a', 'b'],
115
+ not_between_the_letter: ['x', 'z']
116
+ }, @test_range)
117
+ expect(ranged_collection.to_sql).to match(/WHERE/)
118
+ expect(ranged_collection.to_sql).to match(/first_name BETWEEN 'a' AND 'b'/)
119
+ expect(ranged_collection.to_sql).to match(/AND \(first_name NOT BETWEEN 'x' AND 'z'\)/)
120
+ end
121
+
122
+ it 'Multiple ranged' do
123
+ # Desc ranged
124
+ ranged_collection = apply_range_to_collection(@collection, {
125
+ login: ['aaubin', 'qbollach'],
126
+ id: ['74', '76']
127
+ })
128
+ expect(ranged_collection.to_sql).to match(/WHERE/)
129
+ expect(ranged_collection.to_sql).to match(/BETWEEN 'aaubin' AND 'qbollach'/)
130
+ expect(ranged_collection.to_sql).to match(/\) AND \(/)
131
+ expect(ranged_collection.to_sql).to match(/BETWEEN 74 AND 76/)
132
+ end
133
+
134
+ it 'invalid args' do
135
+ # invalid args
136
+ expect do
137
+ apply_range_to_collection(
138
+ @collection,
139
+ {sorted: ['true', 'baguette']},
140
+ format_additional_param({sorted: [-> (v, w) { v }, ['true', 'false', 'maybe']]}, 'ranged')
141
+ )
142
+ end.to raise_error(RiceCooker::InvalidRangeValueException)
143
+ end
144
+ end
145
+
146
+ describe 'Additional params must be correctly formated' do
147
+
148
+ it 'No additional params' do
149
+ formated = format_additional_param({}, 'ranged')
150
+ expect(formated).to be_eql({})
151
+ end
152
+
153
+ it 'Already correctly formatted additional params' do
154
+ p = {range: {
155
+ proc: @proc,
156
+ all: [1, 2, 3],
157
+ description: 'A good filter'
158
+ }}
159
+ formated = format_additional_param(p, 'ranged')
160
+ expect(formated).to be_eql(p)
161
+ end
162
+
163
+ it 'Missing description additional params' do
164
+ p = {filter: {
165
+ proc: @proc,
166
+ all: [1, 2, 3]
167
+ }}
168
+ expected = {filter: {
169
+ proc: @proc,
170
+ all: [1, 2, 3],
171
+ description: ''
172
+ }}
173
+ formated = format_additional_param(p, 'ranged')
174
+ expect(formated).to be_eql(expected)
175
+ end
176
+
177
+ it 'Only proc additional params' do
178
+
179
+ p = {filter: @proc}
180
+ expected = {filter: {
181
+ proc: @proc,
182
+ all: [],
183
+ description: ''
184
+ }}
185
+ formated = format_additional_param(p, 'ranged')
186
+ expect(formated).to be_eql(expected)
187
+ end
188
+
189
+ it 'Array with proc and all additional params' do
190
+
191
+ p = {filter: [@proc, @all]}
192
+ expected = {filter: {
193
+ proc: @proc,
194
+ all: @all,
195
+ description: ''
196
+ }}
197
+ formated = format_additional_param(p, 'ranged')
198
+ expect(formated).to be_eql(expected)
199
+ end
200
+
201
+ it 'Multiple, std + Array with proc and all additional params' do
202
+
203
+ p = {
204
+ tata: @proc,
205
+ toto: {proc: @proc, all: [1, 2]},
206
+ filter: [@proc, @all],
207
+ tutu: {proc: @proc, description: 'Buuuuh'}
208
+ }
209
+ expected = {
210
+ tata: {
211
+ proc: @proc,
212
+ all: [],
213
+ description: ''
214
+ },
215
+ toto: {
216
+ proc: @proc,
217
+ all: [1, 2],
218
+ description: ''
219
+ },
220
+ filter: {
221
+ proc: @proc,
222
+ all: @all,
223
+ description: ''
224
+ },
225
+ tutu: {
226
+ proc: @proc,
227
+ all: [],
228
+ description: 'Buuuuh'
229
+ }
230
+ }
231
+ formated = format_additional_param(p, 'ranged')
232
+ expect(formated).to be_eql(expected)
233
+ end
234
+
235
+ end
236
+ end
237
+
238
+ RSpec.describe UsersController, type: :controller do
239
+ include RiceCooker::Helpers
240
+
241
+ before { request.host = 'example.org' }
242
+
243
+ describe 'GET #index' do
244
+ it 'without range parameter' do
245
+ process :index, method: :get, params: { range: '', format: :json }
246
+ expect(response.body).to eq(User.all.order(id: :desc).to_json)
247
+ end
248
+
249
+ it 'with simple range parameter' do
250
+ process :index, method: :get, params: { range: { login: 'aaubin,qbollach' }, format: :json }
251
+ expect(response.body).to eq(User.where(login: 'aaubin'..'qbollach').order(id: :desc).to_json)
252
+ end
253
+
254
+ it 'with multiple range parameter' do
255
+ process :index, method: :get, params: { range: { login: 'aaubin,qbollach', id: '1,5' }, format: :json }
256
+ expect(response.body).to eq(User.where(login: 'aaubin'..'qbollach', id: 1..5).order(id: :desc).to_json)
257
+ end
258
+
259
+ it 'with invalid range parameter' do
260
+ expect do
261
+ process :index, method: :get, params: { range: { created_at: '2016-02-04,qbollach', login: '^,&' }, format: :json }
262
+ end.to raise_error(RiceCooker::InvalidRangeException)
263
+ end
264
+ end
265
+ end