philtre 0.0.0 → 0.0.1
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 +4 -4
- data/.gitignore +6 -5
- data/.travis.yml +7 -0
- data/README.md +169 -12
- data/Rakefile +8 -0
- data/TODO +0 -0
- data/lib/philtre.rb +59 -2
- data/lib/philtre/core_extensions.rb +31 -0
- data/lib/philtre/empty_expression.rb +9 -0
- data/lib/philtre/filter.rb +232 -0
- data/lib/philtre/grinder.rb +195 -0
- data/lib/philtre/place_holder.rb +41 -0
- data/lib/philtre/predicate_dsl.rb +25 -0
- data/lib/philtre/predicate_splitter.rb +40 -0
- data/lib/philtre/predicates.rb +109 -0
- data/lib/philtre/sequel_extensions.rb +30 -0
- data/lib/philtre/version.rb +2 -2
- data/philtre.gemspec +17 -10
- data/spec/dataset_spec.rb +57 -0
- data/spec/filter_spec.rb +502 -0
- data/spec/grinder_spec.rb +180 -0
- data/spec/predicate_splitter_spec.rb +54 -0
- data/tasks/console.rake +10 -0
- metadata +112 -8
data/philtre.gemspec
CHANGED
@@ -4,20 +4,27 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
require 'philtre/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
7
|
+
spec.name = 'philtre'
|
8
8
|
spec.version = Philtre::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
11
|
-
spec.summary = %q{
|
12
|
-
spec.description = %q{
|
13
|
-
spec.homepage =
|
14
|
-
spec.license =
|
9
|
+
spec.authors = ['John Anderson']
|
10
|
+
spec.email = ['panic@semiosix.com']
|
11
|
+
spec.summary = %q{http parameter-hash friendly filtering for Sequel}
|
12
|
+
spec.description = %q{Encode various filtering operations in http parameter hashes}
|
13
|
+
spec.homepage = 'http://github.com/djellemah/philtre'
|
14
|
+
spec.license = 'MIT'
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
17
17
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
-
spec.require_paths = [
|
19
|
+
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.
|
22
|
-
spec.
|
21
|
+
spec.add_dependency 'sequel'
|
22
|
+
spec.add_dependency 'fastandand'
|
23
|
+
spec.add_dependency 'ripar', '~> 0.0.3'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.5'
|
26
|
+
spec.add_development_dependency 'rake'
|
27
|
+
spec.add_development_dependency 'rspec'
|
28
|
+
spec.add_development_dependency 'faker'
|
29
|
+
spec.add_development_dependency 'sqlite3'
|
23
30
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'faker'
|
3
|
+
require 'sequel'
|
4
|
+
|
5
|
+
require_relative '../lib/philtre/grinder.rb'
|
6
|
+
require_relative '../lib/philtre/sequel_extensions.rb'
|
7
|
+
require_relative '../lib/philtre/core_extensions.rb'
|
8
|
+
|
9
|
+
Sequel.extension :blank
|
10
|
+
Sequel.extension :core_extensions
|
11
|
+
|
12
|
+
describe Sequel::Dataset do
|
13
|
+
subject do
|
14
|
+
Sequel.mock[:t].filter( :name.lieu, :title.lieu ).order( :birth_year.lieu )
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#grind' do
|
18
|
+
it 'generates sql' do
|
19
|
+
subject.grind.sql.should == 'SELECT * FROM t'
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'yields grinder' do
|
23
|
+
# predeclare so it survives the lambda
|
24
|
+
outer_grr = nil
|
25
|
+
subject.grind{|grr| outer_grr = grr }.sql.should == 'SELECT * FROM t'
|
26
|
+
outer_grr.should be_a(Philtre::Grinder)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'passes apply_unknown'
|
31
|
+
|
32
|
+
describe '#roller' do
|
33
|
+
it 'result has to_dataset' do
|
34
|
+
rlr = subject.roller do
|
35
|
+
where title: 'Exalted Fromaginess'
|
36
|
+
end
|
37
|
+
|
38
|
+
# This depends on Ripar, so it's a bit fragile
|
39
|
+
rlr.should respond_to(:__class__)
|
40
|
+
rlr.__class__.should == Ripar::Roller
|
41
|
+
|
42
|
+
rlr.should_not respond_to(:datset)
|
43
|
+
rlr.should respond_to(:to_dataset)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#rolled' do
|
48
|
+
it 'gives back a rolled dataset' do
|
49
|
+
rlr = subject.rolled do
|
50
|
+
where title: 'Exalted Fromaginess'
|
51
|
+
end
|
52
|
+
rlr.should be_a(Sequel::Dataset)
|
53
|
+
rlr.should_not respond_to(:datset)
|
54
|
+
rlr.should_not respond_to(:to_dataset)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/spec/filter_spec.rb
ADDED
@@ -0,0 +1,502 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'faker'
|
3
|
+
|
4
|
+
require_relative '../lib/philtre/filter.rb'
|
5
|
+
|
6
|
+
# for blank?
|
7
|
+
Sequel.extension :blank
|
8
|
+
|
9
|
+
describe Philtre::Filter do
|
10
|
+
# must be in before otherwise it's unpleasant to hook the
|
11
|
+
# class in to the dataset.
|
12
|
+
before :all do
|
13
|
+
@dataset = Sequel.mock[:planks]
|
14
|
+
class Plank < Sequel::Model; end
|
15
|
+
# just stop whining and generate the bleedin' sql, k?
|
16
|
+
def @dataset.supports_regexp?; true end
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :dataset
|
20
|
+
|
21
|
+
describe '#initialize' do
|
22
|
+
it 'keeps parameters' do
|
23
|
+
filter = described_class.new one: 1, two: 2
|
24
|
+
filter.filter_parameters.keys.should == %i[one two]
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'defaults parameters' do
|
28
|
+
described_class.new.filter_parameters.should == {}
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'keeps empty parameters' do
|
32
|
+
described_class.new({}).filter_parameters.should == {}
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'converts non-symbol keys' do
|
36
|
+
filter = described_class.new 'name' => Faker::Lorem.word, 'title' => Faker::Lorem.word, 'order' => 'owner'
|
37
|
+
filter.filter_parameters.keys.should == %i[name title order]
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'treats nil as empty parameters' do
|
41
|
+
filter = described_class.new(nil)
|
42
|
+
filter.filter_parameters.should == {}
|
43
|
+
end
|
44
|
+
|
45
|
+
describe 'custom predicates' do
|
46
|
+
it 'from yield' do
|
47
|
+
outside = 'Outside Value'
|
48
|
+
filter = described_class.new custom_predicate: 'Special Value' do |predicates|
|
49
|
+
# yip, you really do have to use the define_method hack here
|
50
|
+
# to get outside values into the predicates module.
|
51
|
+
predicates.send :define_method, :custom_predicate do | val |
|
52
|
+
{special_field: val, other_special_field: outside}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
filter.apply(dataset).sql.should == %q{SELECT * FROM planks WHERE ((special_field = 'Special Value') AND (other_special_field = 'Outside Value'))}
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'from module_eval' do
|
59
|
+
filter = described_class.new custom_predicate: 'Special Value' do
|
60
|
+
# this is just a normal module block.
|
61
|
+
def custom_predicate( val )
|
62
|
+
{special_field: val}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
filter.apply(dataset).sql.should == %q{SELECT * FROM planks WHERE (special_field = 'Special Value')}
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '#order_expressions' do
|
71
|
+
it 'defaults to asc' do
|
72
|
+
filter = described_class.new one: 1, two: 2, order: 'things'
|
73
|
+
@dataset.order( *filter.order_clause ).sql.should =~ /order by things asc$/i
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe '#order_expr' do
|
78
|
+
filter = described_class.new one: 1, two: 2, order: 'things'
|
79
|
+
|
80
|
+
it 'nil for nil' do
|
81
|
+
filter.order_expr(nil).should be_nil
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'nil for blank' do
|
85
|
+
filter.order_expr('').should be_nil
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'defaults to asc' do
|
89
|
+
filter = described_class.new one: 1, two: 2, order: 'things'
|
90
|
+
sqlfrag = filter.order_expr(:things).sql_literal(dataset)
|
91
|
+
sqlfrag.should == 'things ASC'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '#order_clause' do
|
96
|
+
it '[] for nil order parameter' do
|
97
|
+
filter = described_class.new one: 1, two: 2
|
98
|
+
filter.order_clause.should be_empty
|
99
|
+
end
|
100
|
+
|
101
|
+
it '[] for blank order parameter' do
|
102
|
+
filter = described_class.new one: 1, two: 2, order: ''
|
103
|
+
filter.order_clause.should be_empty
|
104
|
+
end
|
105
|
+
|
106
|
+
# These should really be part of describe '#order_expr'
|
107
|
+
it 'defaults to asc' do
|
108
|
+
filter = described_class.new one: 1, two: 2, order: 'things'
|
109
|
+
@dataset.order( *filter.order_clause ).sql.should =~ /order by things asc$/i
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'respects desc' do
|
113
|
+
filter = described_class.new one: 1, two: 2, order: 'things_desc'
|
114
|
+
@dataset.order( *filter.order_clause ).sql.should =~ /order by things desc$/i
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'respecs asc' do
|
118
|
+
filter = described_class.new one: 1, two: 2, order: 'things_desc'
|
119
|
+
@dataset.order( *filter.order_clause ).sql.should =~ /order by things desc$/i
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'handles array' do
|
123
|
+
filter = described_class.new one: 1, two: 2, order: ['things_desc', 'stuff', 'orgle_asc']
|
124
|
+
@dataset.order( *filter.order_clause ).sql.should =~ /order by things desc, stuff asc, orgle asc$/i
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'handles array with blanks' do
|
128
|
+
filter = described_class.new one: 1, two: 2, order: ['things_desc', nil, 'stuff', '', 'orgle_asc']
|
129
|
+
@dataset.order( *filter.order_clause ).sql.should =~ /order by things desc, stuff asc, orgle asc$/i
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe '#predicates' do
|
134
|
+
EASY_PREDICATES = %i[gt gte gteq lt lte lteq eq not_eq matches like not_like]
|
135
|
+
TRICKY_PREDICATES = %i[like_all like_any not_blank blank]
|
136
|
+
|
137
|
+
it 'creates predicates' do
|
138
|
+
described_class.predicates.predicate_names.sort.should == (EASY_PREDICATES + TRICKY_PREDICATES).sort
|
139
|
+
end
|
140
|
+
|
141
|
+
EASY_PREDICATES.each do |predicate|
|
142
|
+
it "_#{predicate} becomes expression" do
|
143
|
+
field = Faker::Lorem.word.to_sym
|
144
|
+
value = Faker::Lorem.word
|
145
|
+
expr = described_class.predicates.call field, value
|
146
|
+
|
147
|
+
expr.should be_a(Sequel::SQL::BooleanExpression)
|
148
|
+
|
149
|
+
expr.args.first.should be_a(Sequel::SQL::Identifier)
|
150
|
+
expr.args.first.should == Sequel.expr(field)
|
151
|
+
expr.args.last.should == value
|
152
|
+
|
153
|
+
expr.sql_literal(@dataset).should be_a(String)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe 'like_all' do
|
158
|
+
it 'takes one' do
|
159
|
+
field = Faker::Lorem.word
|
160
|
+
value = Faker::Lorem.word
|
161
|
+
expr = described_class.predicates.call :"#{field}_like_all", value
|
162
|
+
expr.args.size.should == 1
|
163
|
+
expr.op.should == :NOOP
|
164
|
+
|
165
|
+
expr.args.first.op.should == :'~*'
|
166
|
+
ident, value = expr.args.first.args
|
167
|
+
ident.value.should == field
|
168
|
+
value.should == value
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'takes many' do
|
172
|
+
field = Faker::Lorem.word
|
173
|
+
value = 3.times.map{ Faker::Lorem.word }
|
174
|
+
expr = described_class.predicates.call :"#{field}_like_all", value
|
175
|
+
expr.args.size.should == 3
|
176
|
+
expr.op.should == :AND
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
describe 'like_any' do
|
181
|
+
it 'takes one' do
|
182
|
+
field = Faker::Lorem.word
|
183
|
+
value = Faker::Lorem.word
|
184
|
+
expr = described_class.predicates.call :"#{field}_like_any", value
|
185
|
+
expr.args.size.should == 1
|
186
|
+
expr.op.should == :NOOP
|
187
|
+
|
188
|
+
expr.args.first.op.should == :'~*'
|
189
|
+
ident, value = expr.args.first.args
|
190
|
+
ident.value.should == field
|
191
|
+
value.should == value
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'takes many' do
|
195
|
+
field = Faker::Lorem.word
|
196
|
+
value = 3.times.map{ Faker::Lorem.word }
|
197
|
+
expr = described_class.predicates.call :"#{field}_like_any", value
|
198
|
+
expr.args.size.should == 3
|
199
|
+
expr.op.should == :OR
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
it 'not_blank' do
|
204
|
+
field = Sequel.expr Faker::Lorem.word
|
205
|
+
expr = described_class.predicates.call :"#{field}_not_blank", Faker::Lorem.word
|
206
|
+
|
207
|
+
expr.op.should == :AND
|
208
|
+
|
209
|
+
# the not-nil part
|
210
|
+
expr.args.first.op.should == :'IS NOT'
|
211
|
+
|
212
|
+
# the not empty string part
|
213
|
+
expr.args.last.op.should == :'!='
|
214
|
+
expr.args.last.args.last.should == ''
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
describe '#to_expr' do
|
219
|
+
let(:filter){ described_class.new name: Faker::Lorem.word, title: Faker::Lorem.word }
|
220
|
+
it 'is == for name only' do
|
221
|
+
expr = Sequel.expr( filter.to_expr( :name, 'hallelujah' ) )
|
222
|
+
expr.op.should == :'='
|
223
|
+
expr.args.first.should be_a(Sequel::SQL::Identifier)
|
224
|
+
expr.args.first.value.should == 'name'
|
225
|
+
expr.args.last.should == 'hallelujah'
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'like' do
|
229
|
+
expr = Sequel.expr( filter.to_expr( :owner_like, 'hallelujah' ) )
|
230
|
+
expr.op.should == :'~*'
|
231
|
+
expr.args.first.should be_a(Sequel::SQL::Identifier)
|
232
|
+
expr.args.first.value.should == 'owner'
|
233
|
+
expr.args.last.should == 'hallelujah'
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'keeps blank values' do
|
237
|
+
filter.to_expr( :owner, '' ).should_not be_nil
|
238
|
+
filter.to_expr( :owner, nil ).should_not be_nil
|
239
|
+
filter.to_expr( :owner, [] ).should_not be_nil
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'substitutes a field name' do
|
243
|
+
expr = Sequel.expr( filter.to_expr( :owner_like, 'hallelujah', :heavens__salutation ) )
|
244
|
+
expr.op.should == :'~*'
|
245
|
+
expr.args.first.should be_kind_of(Sequel::SQL::QualifiedIdentifier)
|
246
|
+
expr.args.first.column.should == 'salutation'
|
247
|
+
expr.args.first.table.should == 'heavens'
|
248
|
+
expr.args.last.should == 'hallelujah'
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'must always be a Sequel::SQL::Expression' do
|
252
|
+
filter.predicates.extend_with do
|
253
|
+
def year_range(jumbled_years)
|
254
|
+
first, last = jumbled_years.sort.instance_eval{|ry| [ry.first, ry.last]}
|
255
|
+
{ year: first..last }
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
expr = filter.to_expr( :year_range, [1984, 1970, 2012] )
|
260
|
+
expr.should be_a(Sequel::SQL::Expression)
|
261
|
+
expr.sql_literal(dataset).should == '((year >= 1970) AND (year <= 2012))'
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
describe '#expr_for' do
|
266
|
+
let(:filter){ described_class.new name: Faker::Lorem.word, title: Faker::Lorem.word, interstellar: '' }
|
267
|
+
|
268
|
+
it 'nil for no value' do
|
269
|
+
filter.expr_for(:bleh).should be_nil
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'nil for no value' do
|
273
|
+
filter.expr_for(:interstellar).should be_nil
|
274
|
+
end
|
275
|
+
|
276
|
+
it 'expression for existing value' do
|
277
|
+
filter.expr_for(:name).should_not be_nil
|
278
|
+
filter.expr_for(:name).should be_a(Sequel::SQL::BooleanExpression)
|
279
|
+
end
|
280
|
+
|
281
|
+
it 'alternate name' do
|
282
|
+
expr = filter.expr_for(:name, :things__name)
|
283
|
+
expr.should_not be_nil
|
284
|
+
expr.should be_a(Sequel::SQL::BooleanExpression)
|
285
|
+
|
286
|
+
expr.args.first.tap do |field_expr|
|
287
|
+
field_expr.column.should == 'name'
|
288
|
+
field_expr.table.should == 'things'
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
describe '#order_for' do
|
294
|
+
let(:filter){ described_class.new name: Faker::Lorem.word, title: Faker::Lorem.word, order:[:name, :title, :year] }
|
295
|
+
let(:dataset){ Sequel.mock[:things] }
|
296
|
+
|
297
|
+
it 'nil for no parameter' do
|
298
|
+
filter.order_for( :icecream_count ).should be_nil
|
299
|
+
end
|
300
|
+
|
301
|
+
it 'ascending' do
|
302
|
+
filter.order_for(:year).sql_literal(dataset).should == 'year ASC'
|
303
|
+
end
|
304
|
+
|
305
|
+
it 'name clash' do
|
306
|
+
filter.order_for(:title).sql_literal(dataset).should == 'title ASC'
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
describe '#expressions' do
|
311
|
+
it 'generates expressions' do
|
312
|
+
expressions = described_class.new( trailer: 'large' ).expressions
|
313
|
+
expressions.size.should == 1
|
314
|
+
expressions.first.should be_a(Sequel::SQL::BooleanExpression)
|
315
|
+
expr, value = expressions.first.args
|
316
|
+
expr.should be_a(Sequel::SQL::Identifier)
|
317
|
+
expr.value.should == 'trailer'
|
318
|
+
value.should == 'large'
|
319
|
+
end
|
320
|
+
|
321
|
+
it 'handles stringified operators' do
|
322
|
+
expressions = described_class.new( trailer_gte: 'large' ).expressions
|
323
|
+
expressions.size.should == 1
|
324
|
+
expressions.first.should be_a(Sequel::SQL::BooleanExpression)
|
325
|
+
expr, value = expressions.first.args
|
326
|
+
expr.should be_a(Sequel::SQL::Identifier)
|
327
|
+
expr.value.should == 'trailer'
|
328
|
+
value.should == 'large'
|
329
|
+
end
|
330
|
+
|
331
|
+
it 'ignores order:' do
|
332
|
+
described_class.new(order: %w[one two tre]).expressions.should be_empty
|
333
|
+
end
|
334
|
+
|
335
|
+
it "ignores '' value" do
|
336
|
+
described_class.new( address: '' ).expressions.should be_empty
|
337
|
+
end
|
338
|
+
|
339
|
+
it 'ignores nil value' do
|
340
|
+
described_class.new( address: nil ).expressions.should be_empty
|
341
|
+
end
|
342
|
+
|
343
|
+
it 'accepts []' do
|
344
|
+
expressions = described_class.new( flavour: [] ).expressions
|
345
|
+
expressions.size.should == 1
|
346
|
+
expressions.first.should be_a(Sequel::SQL::BooleanExpression)
|
347
|
+
expr, value = expressions.first.args
|
348
|
+
expr.should be_a(Sequel::SQL::Identifier)
|
349
|
+
expr.value.should == 'flavour'
|
350
|
+
value.should == []
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
describe '#apply' do
|
355
|
+
let(:filter){ described_class.new name: Faker::Lorem.word, title: Faker::Lorem.word }
|
356
|
+
|
357
|
+
# make sure the Model dataset isn't impacted by setting the ordering
|
358
|
+
# on the filtered dataset.
|
359
|
+
it 'clones' do
|
360
|
+
orig_dataset = Plank.dataset
|
361
|
+
filter.filter_parameters[:order] = :title
|
362
|
+
filter.apply(Plank.dataset)
|
363
|
+
Plank.dataset.should == orig_dataset
|
364
|
+
end
|
365
|
+
|
366
|
+
it 'accepts Sequel::Model subclasses' do
|
367
|
+
ds = filter.apply(Plank)
|
368
|
+
ds.should be_a(Sequel::Dataset)
|
369
|
+
ds.sql.should =~ /planks/
|
370
|
+
end
|
371
|
+
|
372
|
+
it 'filter parameters' do
|
373
|
+
sql = filter.apply(@dataset).sql
|
374
|
+
sql.should =~ /select \* from planks where \(\(name = '\w+'\) and \(title = '\w+'\)\)$/i
|
375
|
+
end
|
376
|
+
|
377
|
+
it 'single order clause' do
|
378
|
+
filter.filter_parameters[:order] = :title
|
379
|
+
filter.apply(@dataset).sql.should =~ /order by.*title/i
|
380
|
+
end
|
381
|
+
|
382
|
+
it 'multiple order clause' do
|
383
|
+
filter.filter_parameters[:order] = [:title, :owner]
|
384
|
+
filter.apply(@dataset).sql.should =~ /order by.*title.*owner/i
|
385
|
+
end
|
386
|
+
|
387
|
+
it 'empty filter parameters' do
|
388
|
+
filter = described_class.new
|
389
|
+
filter.filter_parameters.should be_empty
|
390
|
+
filter.apply(@dataset).sql.should =~ /select \* from planks$/i
|
391
|
+
end
|
392
|
+
|
393
|
+
it 'no order clause' do
|
394
|
+
sql = filter.apply(@dataset).sql
|
395
|
+
sql.should_not =~ /order by/i
|
396
|
+
end
|
397
|
+
|
398
|
+
it 'no order clause keeps previous order clause' do
|
399
|
+
sql = filter.apply(@dataset.order(:watookal)).sql
|
400
|
+
sql.should =~ /order by/i
|
401
|
+
end
|
402
|
+
|
403
|
+
it 'excludes blank values' do
|
404
|
+
filter.filter_parameters[:name] = ''
|
405
|
+
sql = filter.apply(@dataset).sql
|
406
|
+
sql.should =~ /select \* from planks where \(title = '\w+'\)$/i
|
407
|
+
end
|
408
|
+
|
409
|
+
it 'excludes nil values' do
|
410
|
+
filter.filter_parameters[:name] = nil
|
411
|
+
sql = filter.apply(@dataset).sql
|
412
|
+
sql.should =~ /select \* from planks where \(title = '\w+'\)$/i
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
describe '#empty?' do
|
417
|
+
it 'true on no parameters' do
|
418
|
+
described_class.new.should be_empty
|
419
|
+
end
|
420
|
+
|
421
|
+
it 'false with parameters' do
|
422
|
+
described_class.new(one: 1, two: 2).should_not be_empty
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
describe '#subset' do
|
427
|
+
it 'has specified subset of parameter values' do
|
428
|
+
filter = described_class.new done_with: 'Hammers', fixed_by: 'Thor'
|
429
|
+
filter.subset( :done_with ).filter_parameters.keys.should == [:done_with]
|
430
|
+
end
|
431
|
+
|
432
|
+
it 'has block specified subset of parameter values' do
|
433
|
+
filter = described_class.new done_with: 'Hammers', fixed_by: 'Thor'
|
434
|
+
filter.subset{|k,v| k == :done_with}.filter_parameters.keys.should == [:done_with]
|
435
|
+
end
|
436
|
+
|
437
|
+
it 'keeps custom predicates' do
|
438
|
+
filter = described_class.new done_with: 'Hammers' do
|
439
|
+
def done_with( things )
|
440
|
+
Sequel.expr done: things
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
filter.subset( :done_with ).predicates.should respond_to(:done_with)
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
describe '#extract!' do
|
449
|
+
it 'gives back subset' do
|
450
|
+
filter = described_class.new first: 'James', second: 'McDonald', third: 'Fraser'
|
451
|
+
extracted = filter.extract!(:first)
|
452
|
+
extracted.to_h.size.should == 1
|
453
|
+
extracted.to_h.should have_key(:first)
|
454
|
+
end
|
455
|
+
|
456
|
+
it 'removes specified keys' do
|
457
|
+
filter = described_class.new first: 'James', second: 'McDonald', third: 'Fraser'
|
458
|
+
extracted = filter.extract!(:first, :third)
|
459
|
+
filter.to_h.size.should == 1
|
460
|
+
filter.to_h.should have_key(:second)
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
describe '#to_h' do
|
465
|
+
def filter
|
466
|
+
@filter ||= described_class.new first: 'James', second: 'McDonald', third: 'Fraser', fourth: '', fifth: nil
|
467
|
+
end
|
468
|
+
|
469
|
+
it 'all values' do
|
470
|
+
filter.to_h(true).size.should == filter.filter_parameters.size
|
471
|
+
end
|
472
|
+
|
473
|
+
it 'only non-blank values' do
|
474
|
+
filter.to_h.size.should == 3
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
describe '#clone' do
|
479
|
+
it 'plain clone' do
|
480
|
+
filter = described_class.new first: 'James', second: 'McDonald', third: 'Fraser'
|
481
|
+
cloned = filter.clone
|
482
|
+
cloned.filter_parameters.should == filter.filter_parameters
|
483
|
+
end
|
484
|
+
|
485
|
+
it 'clone with extras leaves original' do
|
486
|
+
value_hash = {first: 'James', second: 'McDonald', third: 'Fraser'}.freeze
|
487
|
+
filter = described_class.new value_hash
|
488
|
+
cloned = filter.clone( extra: 'Magoodies')
|
489
|
+
|
490
|
+
filter.filter_parameters.should == value_hash
|
491
|
+
end
|
492
|
+
|
493
|
+
it 'clone with extras adds values' do
|
494
|
+
value_hash = {first: 'James', second: 'McDonald', third: 'Fraser'}.freeze
|
495
|
+
filter = described_class.new value_hash
|
496
|
+
cloned = filter.clone( extra: 'Magoodies')
|
497
|
+
|
498
|
+
(cloned.filter_parameters.keys & filter.filter_parameters.keys).should == filter.filter_parameters.keys
|
499
|
+
(cloned.filter_parameters.keys - filter.filter_parameters.keys).should == [:extra]
|
500
|
+
end
|
501
|
+
end
|
502
|
+
end
|