ransack 4.4.0 → 4.4.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.
@@ -1,814 +0,0 @@
1
- require 'spec_helper'
2
-
3
- module Ransack
4
- describe Search do
5
- describe '#initialize' do
6
- it 'removes empty conditions before building' do
7
- expect_any_instance_of(Search).to receive(:build).with({})
8
- Search.new(Person, name_eq: '')
9
- end
10
-
11
- it 'keeps conditions with a false value before building' do
12
- expect_any_instance_of(Search).to receive(:build)
13
- .with({ 'name_eq' => false })
14
- Search.new(Person, name_eq: false)
15
- end
16
-
17
- it 'keeps conditions with a value before building' do
18
- expect_any_instance_of(Search).to receive(:build)
19
- .with({ 'name_eq' => 'foobar' })
20
- Search.new(Person, name_eq: 'foobar')
21
- end
22
-
23
- context 'whitespace stripping' do
24
- context 'when whitespace_strip option is true' do
25
- before do
26
- Ransack.configure { |c| c.strip_whitespace = true }
27
- end
28
-
29
- it 'strips leading & trailing whitespace before building' do
30
- expect_any_instance_of(Search).to receive(:build)
31
- .with({ 'name_eq' => 'foobar' })
32
- Search.new(Person, name_eq: ' foobar ')
33
- end
34
- end
35
-
36
- context 'when whitespace_strip option is false' do
37
- before do
38
- Ransack.configure { |c| c.strip_whitespace = false }
39
- end
40
-
41
- it 'doesn\'t strip leading & trailing whitespace before building' do
42
- expect_any_instance_of(Search).to receive(:build)
43
- .with({ 'name_eq' => ' foobar ' })
44
- Search.new(Person, name_eq: ' foobar ')
45
- end
46
- end
47
-
48
- it 'strips leading & trailing whitespace when strip_whitespace search parameter is true' do
49
- expect_any_instance_of(Search).to receive(:build)
50
- .with({ 'name_eq' => 'foobar' })
51
- Search.new(Person, { name_eq: ' foobar ' }, { strip_whitespace: true })
52
- end
53
-
54
- it 'doesn\'t strip leading & trailing whitespace when strip_whitespace search parameter is false' do
55
- expect_any_instance_of(Search).to receive(:build)
56
- .with({ 'name_eq' => ' foobar ' })
57
- Search.new(Person, { name_eq: ' foobar ' }, { strip_whitespace: false })
58
- end
59
- end
60
-
61
- it 'removes empty suffixed conditions before building' do
62
- expect_any_instance_of(Search).to receive(:build).with({})
63
- Search.new(Person, name_eq_any: [''])
64
- end
65
-
66
- it 'keeps suffixed conditions with a false value before building' do
67
- expect_any_instance_of(Search).to receive(:build)
68
- .with({ 'name_eq_any' => [false] })
69
- Search.new(Person, name_eq_any: [false])
70
- end
71
-
72
- it 'keeps suffixed conditions with a value before building' do
73
- expect_any_instance_of(Search).to receive(:build)
74
- .with({ 'name_eq_any' => ['foobar'] })
75
- Search.new(Person, name_eq_any: ['foobar'])
76
- end
77
-
78
- it 'does not raise exception for string :params argument' do
79
- expect { Search.new(Person, '') }.not_to raise_error
80
- end
81
-
82
- it 'accepts a context option' do
83
- shared_context = Context.for(Person)
84
- s1 = Search.new(Person, { name_eq: 'A' }, context: shared_context)
85
- s2 = Search.new(Person, { name_eq: 'B' }, context: shared_context)
86
- expect(s1.context).to be s2.context
87
- end
88
- end
89
-
90
- describe '#build' do
91
- it 'creates conditions for top-level attributes' do
92
- s = Search.new(Person, name_eq: 'Ernie')
93
- condition = s.base[:name_eq]
94
- expect(condition).to be_a Nodes::Condition
95
- expect(condition.predicate.name).to eq 'eq'
96
- expect(condition.attributes.first.name).to eq 'name'
97
- expect(condition.value).to eq 'Ernie'
98
- end
99
-
100
- it 'creates conditions for association attributes' do
101
- s = Search.new(Person, children_name_eq: 'Ernie')
102
- condition = s.base[:children_name_eq]
103
- expect(condition).to be_a Nodes::Condition
104
- expect(condition.predicate.name).to eq 'eq'
105
- expect(condition.attributes.first.name).to eq 'children_name'
106
- expect(condition.value).to eq 'Ernie'
107
- end
108
-
109
- it 'creates conditions for polymorphic belongs_to association attributes' do
110
- s = Search.new(Note, notable_of_Person_type_name_eq: 'Ernie')
111
- condition = s.base[:notable_of_Person_type_name_eq]
112
- expect(condition).to be_a Nodes::Condition
113
- expect(condition.predicate.name).to eq 'eq'
114
- expect(condition.attributes.first.name)
115
- .to eq 'notable_of_Person_type_name'
116
- expect(condition.value).to eq 'Ernie'
117
- end
118
-
119
- it 'creates conditions for multiple polymorphic belongs_to association
120
- attributes' do
121
- s = Search.new(Note,
122
- notable_of_Person_type_name_or_notable_of_Article_type_title_eq: 'Ernie')
123
- condition = s.
124
- base[:notable_of_Person_type_name_or_notable_of_Article_type_title_eq]
125
- expect(condition).to be_a Nodes::Condition
126
- expect(condition.predicate.name).to eq 'eq'
127
- expect(condition.attributes.first.name)
128
- .to eq 'notable_of_Person_type_name'
129
- expect(condition.attributes.last.name)
130
- .to eq 'notable_of_Article_type_title'
131
- expect(condition.value).to eq 'Ernie'
132
- end
133
-
134
- it 'creates conditions for aliased attributes',
135
- if: Ransack::SUPPORTS_ATTRIBUTE_ALIAS do
136
- s = Search.new(Person, full_name_eq: 'Ernie')
137
- condition = s.base[:full_name_eq]
138
- expect(condition).to be_a Nodes::Condition
139
- expect(condition.predicate.name).to eq 'eq'
140
- expect(condition.attributes.first.name).to eq 'full_name'
141
- expect(condition.value).to eq 'Ernie'
142
- end
143
-
144
- it 'preserves default scope and conditions for associations' do
145
- s = Search.new(Person, published_articles_title_eq: 'Test')
146
- expect(s.result.to_sql).to include 'default_scope'
147
- expect(s.result.to_sql).to include 'published'
148
- end
149
-
150
- # The failure/oversight in Ransack::Nodes::Condition#arel_predicate or deeper is beyond my understanding of the structures
151
- it 'preserves (inverts) default scope and conditions for negative subqueries' do
152
- # the positive case (published_articles_title_eq) is
153
- # SELECT "people".* FROM "people"
154
- # LEFT OUTER JOIN "articles" ON "articles"."person_id" = "people"."id"
155
- # AND "articles"."published" = 't'
156
- # AND ('default_scope' = 'default_scope')
157
- # WHERE "articles"."title" = 'Test' ORDER BY "people"."id" DESC
158
- #
159
- # negative case was
160
- # SELECT "people".* FROM "people" WHERE "people"."id" NOT IN (
161
- # SELECT "articles"."person_id" FROM "articles"
162
- # WHERE "articles"."person_id" = "people"."id"
163
- # AND NOT ("articles"."title" != 'Test')
164
- # ) ORDER BY "people"."id" DESC
165
- #
166
- # Should have been like
167
- # SELECT "people".* FROM "people" WHERE "people"."id" NOT IN (
168
- # SELECT "articles"."person_id" FROM "articles"
169
- # WHERE "articles"."person_id" = "people"."id"
170
- # AND "articles"."title" = 'Test' AND "articles"."published" = 't' AND ('default_scope' = 'default_scope')
171
- # ) ORDER BY "people"."id" DESC
172
- #
173
- # With tenanting (eg default_scope with column reference), NOT IN should be like
174
- # SELECT "people".* FROM "people" WHERE "people"."tenant_id" = 'tenant_id' AND "people"."id" NOT IN (
175
- # SELECT "articles"."person_id" FROM "articles"
176
- # WHERE "articles"."person_id" = "people"."id"
177
- # AND "articles"."tenant_id" = 'tenant_id'
178
- # AND "articles"."title" = 'Test' AND "articles"."published" = 't' AND ('default_scope' = 'default_scope')
179
- # ) ORDER BY "people"."id" DESC
180
-
181
- s = Search.new(Person, published_articles_title_not_eq: 'Test')
182
- expect(s.result.to_sql).to include 'default_scope'
183
- expect(s.result.to_sql).to include 'published'
184
- end
185
-
186
- it 'discards empty conditions' do
187
- s = Search.new(Person, children_name_eq: '')
188
- condition = s.base[:children_name_eq]
189
- expect(condition).to be_nil
190
- end
191
-
192
- it 'accepts base grouping condition as an option' do
193
- expect(Nodes::Grouping).to receive(:new).with(kind_of(Context), 'or')
194
- Search.new(Person, {}, { grouping: 'or' })
195
- end
196
-
197
- it 'accepts arrays of groupings' do
198
- s = Search.new(Person,
199
- g: [
200
- { m: 'or', name_eq: 'Ernie', children_name_eq: 'Ernie' },
201
- { m: 'or', name_eq: 'Bert', children_name_eq: 'Bert' },
202
- ]
203
- )
204
- ors = s.groupings
205
- expect(ors.size).to eq(2)
206
- or1, or2 = ors
207
- expect(or1).to be_a Nodes::Grouping
208
- expect(or1.combinator).to eq 'or'
209
- expect(or2).to be_a Nodes::Grouping
210
- expect(or2.combinator).to eq 'or'
211
- end
212
-
213
- it 'accepts attributes hashes for groupings' do
214
- s = Search.new(Person,
215
- g: {
216
- '0' => { m: 'or', name_eq: 'Ernie', children_name_eq: 'Ernie' },
217
- '1' => { m: 'or', name_eq: 'Bert', children_name_eq: 'Bert' },
218
- }
219
- )
220
- ors = s.groupings
221
- expect(ors.size).to eq(2)
222
- or1, or2 = ors
223
- expect(or1).to be_a Nodes::Grouping
224
- expect(or1.combinator).to eq 'or'
225
- expect(or2).to be_a Nodes::Grouping
226
- expect(or2.combinator).to eq 'or'
227
- end
228
-
229
- it 'accepts attributes hashes for conditions' do
230
- s = Search.new(Person,
231
- c: {
232
- '0' => { a: ['name'], p: 'eq', v: ['Ernie'] },
233
- '1' => {
234
- a: ['children_name', 'parent_name'],
235
- p: 'eq', v: ['Ernie'], m: 'or'
236
- }
237
- }
238
- )
239
- conditions = s.base.conditions
240
- expect(conditions.size).to eq(2)
241
- expect(conditions.map { |c| c.class })
242
- .to eq [Nodes::Condition, Nodes::Condition]
243
- end
244
-
245
- it 'creates conditions for custom predicates that take arrays' do
246
- Ransack.configure do |config|
247
- config.add_predicate 'ary_pred', wants_array: true
248
- end
249
-
250
- s = Search.new(Person, name_ary_pred: ['Ernie', 'Bert'])
251
- condition = s.base[:name_ary_pred]
252
- expect(condition).to be_a Nodes::Condition
253
- expect(condition.predicate.name).to eq 'ary_pred'
254
- expect(condition.attributes.first.name).to eq 'name'
255
- expect(condition.value).to eq ['Ernie', 'Bert']
256
- end
257
-
258
- it 'does not evaluate the query on #inspect' do
259
- s = Search.new(Person, children_id_in: [1, 2, 3])
260
- expect(s.inspect).not_to match /ActiveRecord/
261
- end
262
-
263
- context 'with an invalid condition' do
264
- subject { Search.new(Person, unknown_attr_eq: 'Ernie') }
265
-
266
- context 'when ignore_unknown_conditions configuration option is false' do
267
- before do
268
- Ransack.configure { |c| c.ignore_unknown_conditions = false }
269
- end
270
-
271
- specify { expect { subject }.to raise_error ArgumentError }
272
- specify { expect { subject }.to raise_error InvalidSearchError }
273
- end
274
-
275
- context 'when ignore_unknown_conditions configuration option is true' do
276
- before do
277
- Ransack.configure { |c| c.ignore_unknown_conditions = true }
278
- end
279
-
280
- specify { expect { subject }.not_to raise_error }
281
- end
282
-
283
- subject(:with_ignore_unknown_conditions_false) {
284
- Search.new(Person,
285
- { unknown_attr_eq: 'Ernie' },
286
- { ignore_unknown_conditions: false }
287
- )
288
- }
289
-
290
- subject(:with_ignore_unknown_conditions_true) {
291
- Search.new(Person,
292
- { unknown_attr_eq: 'Ernie' },
293
- { ignore_unknown_conditions: true }
294
- )
295
- }
296
-
297
- context 'when ignore_unknown_conditions search parameter is absent' do
298
- specify { expect { subject }.not_to raise_error }
299
- end
300
-
301
- context 'when ignore_unknown_conditions search parameter is false' do
302
- specify { expect { with_ignore_unknown_conditions_false }.to raise_error ArgumentError }
303
- specify { expect { with_ignore_unknown_conditions_false }.to raise_error InvalidSearchError }
304
- end
305
-
306
- context 'when ignore_unknown_conditions search parameter is true' do
307
- specify { expect { with_ignore_unknown_conditions_true }.not_to raise_error }
308
- end
309
- end
310
-
311
- it 'does not modify the parameters' do
312
- params = { name_eq: '' }
313
- expect { Search.new(Person, params) }.not_to change { params }
314
- end
315
-
316
- context "ransackable_scope" do
317
- around(:each) do |example|
318
- Person.define_singleton_method(:name_eq) do |name|
319
- self.where(name: name)
320
- end
321
-
322
- begin
323
- example.run
324
- ensure
325
- Person.singleton_class.undef_method :name_eq
326
- end
327
- end
328
-
329
- it "is prioritized over base predicates" do
330
- allow(Person).to receive(:ransackable_scopes)
331
- .and_return(Person.ransackable_scopes + [:name_eq])
332
-
333
- s = Search.new(Person, name_eq: "Johny")
334
- expect(s.instance_variable_get(:@scope_args)["name_eq"]).to eq("Johny")
335
- expect(s.base[:name_eq]).to be_nil
336
- end
337
- end
338
-
339
- context "ransackable_scope with array arguments" do
340
- around(:each) do |example|
341
- Person.define_singleton_method(:domestic) do |countries|
342
- self.where(name: countries)
343
- end
344
-
345
- Person.define_singleton_method(:flexible_scope) do |*args|
346
- self.where(id: args)
347
- end
348
-
349
- Person.define_singleton_method(:two_param_scope) do |param1, param2|
350
- self.where(name: param1, id: param2)
351
- end
352
-
353
- begin
354
- example.run
355
- ensure
356
- Person.singleton_class.undef_method :domestic
357
- Person.singleton_class.undef_method :flexible_scope
358
- Person.singleton_class.undef_method :two_param_scope
359
- end
360
- end
361
-
362
- it "handles scopes that take arrays as single arguments (arity 1)" do
363
- allow(Person).to receive(:ransackable_scopes)
364
- .and_return(Person.ransackable_scopes + [:domestic])
365
-
366
- # This should not raise ArgumentError
367
- expect {
368
- s = Search.new(Person, domestic: ['US', 'JP'])
369
- s.result # This triggers the actual scope call
370
- }.not_to raise_error
371
-
372
- s = Search.new(Person, domestic: ['US', 'JP'])
373
- expect(s.instance_variable_get(:@scope_args)["domestic"]).to eq("US")
374
- end
375
-
376
- it "handles scopes with flexible arity (negative arity)" do
377
- allow(Person).to receive(:ransackable_scopes)
378
- .and_return(Person.ransackable_scopes + [:flexible_scope])
379
-
380
- expect {
381
- s = Search.new(Person, flexible_scope: ['US', 'JP'])
382
- s.result
383
- }.not_to raise_error
384
- end
385
-
386
- it "handles scopes with arity > 1" do
387
- allow(Person).to receive(:ransackable_scopes)
388
- .and_return(Person.ransackable_scopes + [:two_param_scope])
389
-
390
- expect {
391
- s = Search.new(Person, two_param_scope: ['param1', 'param2'])
392
- s.result
393
- }.not_to raise_error
394
- end
395
-
396
- it "still supports the workaround with nested arrays" do
397
- allow(Person).to receive(:ransackable_scopes)
398
- .and_return(Person.ransackable_scopes + [:domestic])
399
-
400
- # The workaround from the issue should still work
401
- expect {
402
- s = Search.new(Person, domestic: [['US', 'JP']])
403
- s.result
404
- }.not_to raise_error
405
- end
406
- end
407
- end
408
-
409
- describe '#result' do
410
- let(:people_name_field) {
411
- "#{quote_table_name("people")}.#{quote_column_name("name")}"
412
- }
413
- let(:people_temperament_field) {
414
- "#{quote_table_name("people")}.#{quote_column_name("temperament")}"
415
- }
416
- let(:children_people_name_field) {
417
- "#{quote_table_name("children_people")}.#{quote_column_name("name")}"
418
- }
419
- let(:notable_type_field) {
420
- "#{quote_table_name("notes")}.#{quote_column_name("notable_type")}"
421
- }
422
-
423
- it 'evaluates conditions contextually' do
424
- s = Search.new(Person, children_name_eq: 'Ernie')
425
- expect(s.result).to be_an ActiveRecord::Relation
426
- expect(s.result.to_sql).to match /#{
427
- children_people_name_field} = 'Ernie'/
428
- end
429
-
430
- context 'when evaluating enums' do
431
- before do
432
- Person.take.update_attribute(:temperament, 'choleric')
433
- end
434
-
435
- it 'evaluates enum key correctly' do
436
- s = Search.new(Person, temperament_eq: 'choleric')
437
-
438
- expect(s.result.to_sql).not_to match /#{
439
- people_temperament_field} = 0/
440
-
441
- expect(s.result.to_sql).to match /#{
442
- people_temperament_field} = #{Person.temperaments[:choleric]}/
443
-
444
- expect(s.result).not_to be_empty
445
- end
446
-
447
- it 'evaluates enum value correctly' do
448
- s = Search.new(Person, temperament_eq: Person.temperaments[:choleric])
449
-
450
- expect(s.result.to_sql).not_to match /#{
451
- people_temperament_field} = 0/
452
-
453
- expect(s.result.to_sql).to match /#{
454
- people_temperament_field} = #{Person.temperaments[:choleric]}/
455
-
456
- expect(s.result).not_to be_empty
457
- end
458
- end
459
-
460
- it 'use appropriate table alias' do
461
- s = Search.new(Person, {
462
- name_eq: "person_name_query",
463
- articles_title_eq: "person_article_title_query",
464
- parent_name_eq: "parent_name_query",
465
- parent_articles_title_eq: 'parents_article_title_query'
466
- }).result
467
-
468
- real_query = remove_quotes_and_backticks(s.to_sql)
469
-
470
- expect(real_query)
471
- .to match(%r{LEFT OUTER JOIN articles ON (\('default_scope' = 'default_scope'\) AND )?articles.person_id = people.id})
472
- expect(real_query)
473
- .to match(%r{LEFT OUTER JOIN articles articles_people ON (\('default_scope' = 'default_scope'\) AND )?articles_people.person_id = parents_people.id})
474
-
475
- expect(real_query)
476
- .to include "people.name = 'person_name_query'"
477
- expect(real_query)
478
- .to include "articles.title = 'person_article_title_query'"
479
- expect(real_query)
480
- .to include "parents_people.name = 'parent_name_query'"
481
- expect(real_query)
482
- .to include "articles_people.title = 'parents_article_title_query'"
483
- end
484
-
485
- it 'evaluates conditions for multiple `belongs_to` associations to the same table contextually' do
486
- s = Search.new(
487
- Recommendation,
488
- person_name_eq: 'Ernie',
489
- target_person_parent_name_eq: 'Test'
490
- ).result
491
- expect(s).to be_an ActiveRecord::Relation
492
- real_query = remove_quotes_and_backticks(s.to_sql)
493
- expected_query = <<-SQL
494
- SELECT recommendations.* FROM recommendations
495
- LEFT OUTER JOIN people ON people.id = recommendations.person_id
496
- LEFT OUTER JOIN people target_people_recommendations
497
- ON target_people_recommendations.id = recommendations.target_person_id
498
- LEFT OUTER JOIN people parents_people
499
- ON parents_people.id = target_people_recommendations.parent_id
500
- WHERE (people.name = 'Ernie' AND parents_people.name = 'Test')
501
- SQL
502
- .squish
503
- expect(real_query).to eq expected_query
504
- end
505
-
506
- it 'evaluates compound conditions contextually' do
507
- s = Search.new(Person, children_name_or_name_eq: 'Ernie').result
508
- expect(s).to be_an ActiveRecord::Relation
509
- expect(s.to_sql).to match /#{children_people_name_field
510
- } = 'Ernie' OR #{people_name_field} = 'Ernie'/
511
- end
512
-
513
- it 'evaluates polymorphic belongs_to association conditions contextually' do
514
- s = Search.new(Note, notable_of_Person_type_name_eq: 'Ernie').result
515
- expect(s).to be_an ActiveRecord::Relation
516
- expect(s.to_sql).to match /#{people_name_field} = 'Ernie'/
517
- expect(s.to_sql).to match /#{notable_type_field} = 'Person'/
518
- end
519
-
520
- it 'evaluates nested conditions' do
521
- s = Search.new(Person, children_name_eq: 'Ernie',
522
- g: [
523
- { m: 'or', name_eq: 'Ernie', children_children_name_eq: 'Ernie' }
524
- ]
525
- ).result
526
- expect(s).to be_an ActiveRecord::Relation
527
- first, last = s.to_sql.split(/ AND /)
528
- expect(first).to match /#{children_people_name_field} = 'Ernie'/
529
- expect(last).to match /#{
530
- people_name_field} = 'Ernie' OR #{
531
- quote_table_name("children_people_2")}.#{
532
- quote_column_name("name")} = 'Ernie'/
533
- end
534
-
535
- it 'evaluates arrays of groupings' do
536
- s = Search.new(Person,
537
- g: [
538
- { m: 'or', name_eq: 'Ernie', children_name_eq: 'Ernie' },
539
- { m: 'or', name_eq: 'Bert', children_name_eq: 'Bert' }
540
- ]
541
- ).result
542
- expect(s).to be_an ActiveRecord::Relation
543
- first, last = s.to_sql.split(/ AND /)
544
- expect(first).to match /#{people_name_field} = 'Ernie' OR #{
545
- children_people_name_field} = 'Ernie'/
546
- expect(last).to match /#{people_name_field} = 'Bert' OR #{
547
- children_people_name_field} = 'Bert'/
548
- end
549
-
550
- it 'returns distinct records when passed distinct: true' do
551
- s = Search.new(Person,
552
- g: [
553
- { m: 'or', comments_body_cont: 'e', articles_comments_body_cont: 'e' }
554
- ]
555
- )
556
-
557
- all_or_load, uniq_or_distinct = :load, :distinct
558
- expect(s.result.send(all_or_load).size)
559
- .to eq(9000)
560
- expect(s.result(distinct: true).size)
561
- .to eq(10)
562
- expect(s.result.send(all_or_load).send(uniq_or_distinct))
563
- .to eq s.result(distinct: true).send(all_or_load)
564
- end
565
-
566
- it 'evaluates joins with belongs_to join' do
567
- s = Person.joins(:parent).ransack(parent_name_eq: 'Ernie').result(distinct: true)
568
- expect(s).to be_an ActiveRecord::Relation
569
- end
570
-
571
- private
572
-
573
- def remove_quotes_and_backticks(str)
574
- str.gsub(/["`]/, '')
575
- end
576
- end
577
-
578
- describe '#sorts=' do
579
- before do
580
- @s = Search.new(Person)
581
- end
582
-
583
- it 'doesn\'t creates sorts' do
584
- @s.sorts = ''
585
- expect(@s.sorts.size).to eq(0)
586
- end
587
-
588
- it 'creates sorts based on a single attribute/direction' do
589
- @s.sorts = 'id desc'
590
- expect(@s.sorts.size).to eq(1)
591
- sort = @s.sorts.first
592
- expect(sort).to be_a Nodes::Sort
593
- expect(sort.name).to eq 'id'
594
- expect(sort.dir).to eq 'desc'
595
- end
596
-
597
- it 'creates sorts based on a single attribute and uppercase direction' do
598
- @s.sorts = 'id DESC'
599
- expect(@s.sorts.size).to eq(1)
600
- sort = @s.sorts.first
601
- expect(sort).to be_a Nodes::Sort
602
- expect(sort.name).to eq 'id'
603
- expect(sort.dir).to eq 'desc'
604
- end
605
-
606
- it 'creates sorts based on a single attribute and without direction' do
607
- @s.sorts = 'id'
608
- expect(@s.sorts.size).to eq(1)
609
- sort = @s.sorts.first
610
- expect(sort).to be_a Nodes::Sort
611
- expect(sort.name).to eq 'id'
612
- expect(sort.dir).to eq 'asc'
613
- end
614
-
615
- it 'creates sorts based on a single alias/direction' do
616
- @s.sorts = 'daddy desc'
617
- expect(@s.sorts.size).to eq(1)
618
- sort = @s.sorts.first
619
- expect(sort).to be_a Nodes::Sort
620
- expect(sort.name).to eq 'parent_name'
621
- expect(sort.dir).to eq 'desc'
622
- end
623
-
624
- it 'creates sorts based on a single alias and uppercase direction' do
625
- @s.sorts = 'daddy DESC'
626
- expect(@s.sorts.size).to eq(1)
627
- sort = @s.sorts.first
628
- expect(sort).to be_a Nodes::Sort
629
- expect(sort.name).to eq 'parent_name'
630
- expect(sort.dir).to eq 'desc'
631
- end
632
-
633
- it 'creates sorts based on a single alias and without direction' do
634
- @s.sorts = 'daddy'
635
- expect(@s.sorts.size).to eq(1)
636
- sort = @s.sorts.first
637
- expect(sort).to be_a Nodes::Sort
638
- expect(sort.name).to eq 'parent_name'
639
- expect(sort.dir).to eq 'asc'
640
- end
641
-
642
- it 'creates sorts based on attributes, alias and directions in array format' do
643
- @s.sorts = ['id desc', { name: 'daddy', dir: 'asc' }]
644
- expect(@s.sorts.size).to eq(2)
645
- sort1, sort2 = @s.sorts
646
- expect(sort1).to be_a Nodes::Sort
647
- expect(sort1.name).to eq 'id'
648
- expect(sort1.dir).to eq 'desc'
649
- expect(sort2).to be_a Nodes::Sort
650
- expect(sort2.name).to eq 'parent_name'
651
- expect(sort2.dir).to eq 'asc'
652
- end
653
-
654
- it 'creates sorts based on attributes, alias and uppercase directions in array format' do
655
- @s.sorts = ['id DESC', { name: 'daddy', dir: 'ASC' }]
656
- expect(@s.sorts.size).to eq(2)
657
- sort1, sort2 = @s.sorts
658
- expect(sort1).to be_a Nodes::Sort
659
- expect(sort1.name).to eq 'id'
660
- expect(sort1.dir).to eq 'desc'
661
- expect(sort2).to be_a Nodes::Sort
662
- expect(sort2.name).to eq 'parent_name'
663
- expect(sort2.dir).to eq 'asc'
664
- end
665
-
666
- it 'creates sorts based on attributes, alias and different directions
667
- in array format' do
668
- @s.sorts = ['id DESC', { name: 'daddy', dir: nil }]
669
- expect(@s.sorts.size).to eq(2)
670
- sort1, sort2 = @s.sorts
671
- expect(sort1).to be_a Nodes::Sort
672
- expect(sort1.name).to eq 'id'
673
- expect(sort1.dir).to eq 'desc'
674
- expect(sort2).to be_a Nodes::Sort
675
- expect(sort2.name).to eq 'parent_name'
676
- expect(sort2.dir).to eq 'asc'
677
- end
678
-
679
- it 'creates sorts based on attributes, alias and directions in hash format' do
680
- @s.sorts = {
681
- '0' => { name: 'id', dir: 'desc' },
682
- '1' => { name: 'daddy', dir: 'asc' }
683
- }
684
- expect(@s.sorts.size).to eq(2)
685
- expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
686
- id_sort = @s.sorts.detect { |s| s.name == 'id' }
687
- daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
688
- expect(id_sort.dir).to eq 'desc'
689
- expect(daddy_sort.dir).to eq 'asc'
690
- end
691
-
692
- it 'creates sorts based on attributes, alias and uppercase directions
693
- in hash format' do
694
- @s.sorts = {
695
- '0' => { name: 'id', dir: 'DESC' },
696
- '1' => { name: 'daddy', dir: 'ASC' }
697
- }
698
- expect(@s.sorts.size).to eq(2)
699
- expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
700
- id_sort = @s.sorts.detect { |s| s.name == 'id' }
701
- daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
702
- expect(id_sort.dir).to eq 'desc'
703
- expect(daddy_sort.dir).to eq 'asc'
704
- end
705
-
706
- it 'creates sorts based on attributes, alias and different directions
707
- in hash format' do
708
- @s.sorts = {
709
- '0' => { name: 'id', dir: 'DESC' },
710
- '1' => { name: 'daddy', dir: nil }
711
- }
712
- expect(@s.sorts.size).to eq(2)
713
- expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
714
- id_sort = @s.sorts.detect { |s| s.name == 'id' }
715
- daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
716
- expect(id_sort.dir).to eq 'desc'
717
- expect(daddy_sort.dir).to eq 'asc'
718
- end
719
-
720
- it 'overrides existing sort' do
721
- @s.sorts = 'id asc'
722
- expect(@s.result.first.id).to eq 1
723
- end
724
-
725
- it 'raises ArgumentError when an invalid argument is sent' do
726
- expect do
727
- @s.sorts = 1234
728
- end.to raise_error(ArgumentError, "Invalid argument (Integer) supplied to sorts=")
729
- end
730
-
731
- it 'raises InvalidSearchError when an invalid argument is sent' do
732
- expect do
733
- @s.sorts = 1234
734
- end.to raise_error(Ransack::InvalidSearchError, "Invalid argument (Integer) supplied to sorts=")
735
- end
736
-
737
- it "PG's sort option", if: ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" do
738
- default = Ransack.options.clone
739
-
740
- s = Search.new(Person, s: 'name asc')
741
- expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC"
742
-
743
- Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_first }
744
- s = Search.new(Person, s: 'name asc')
745
- expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC NULLS FIRST"
746
- s = Search.new(Person, s: 'name desc')
747
- expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" DESC NULLS LAST"
748
-
749
- Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_last }
750
- s = Search.new(Person, s: 'name asc')
751
- expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC NULLS LAST"
752
- s = Search.new(Person, s: 'name desc')
753
- expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" DESC NULLS FIRST"
754
-
755
- Ransack.options = default
756
- end
757
-
758
- it "PG's sort option with double name", if: ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" do
759
- default = Ransack.options.clone
760
-
761
- s = Search.new(Person, s: 'doubled_name asc')
762
- expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC"
763
-
764
- Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_first }
765
- s = Search.new(Person, s: 'doubled_name asc')
766
- expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS FIRST"
767
- s = Search.new(Person, s: 'doubled_name desc')
768
- expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS LAST"
769
-
770
- Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_last }
771
- s = Search.new(Person, s: 'doubled_name asc')
772
- expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS LAST"
773
- s = Search.new(Person, s: 'doubled_name desc')
774
- expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS FIRST"
775
-
776
- Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_always_first }
777
- s = Search.new(Person, s: 'doubled_name asc')
778
- expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS FIRST"
779
- s = Search.new(Person, s: 'doubled_name desc')
780
- expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS FIRST"
781
-
782
- Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_always_last }
783
- s = Search.new(Person, s: 'doubled_name asc')
784
- expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS LAST"
785
- s = Search.new(Person, s: 'doubled_name desc')
786
- expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS LAST"
787
-
788
- Ransack.options = default
789
- end
790
- end
791
-
792
- describe '#method_missing' do
793
- before do
794
- @s = Search.new(Person)
795
- end
796
-
797
- it 'raises NoMethodError when sent an invalid attribute' do
798
- expect { @s.blah }.to raise_error NoMethodError
799
- end
800
-
801
- it 'sets condition attributes when sent valid attributes' do
802
- @s.name_eq = 'Ernie'
803
- expect(@s.name_eq).to eq 'Ernie'
804
- end
805
-
806
- it 'allows chaining to access nested conditions' do
807
- @s.groupings = [
808
- { m: 'or', name_eq: 'Ernie', children_name_eq: 'Ernie' }
809
- ]
810
- expect(@s.groupings.first.children_name_eq).to eq 'Ernie'
811
- end
812
- end
813
- end
814
- end