ransack 2.3.2 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/SECURITY.md +12 -0
  4. data/.github/workflows/cronjob.yml +102 -0
  5. data/.github/workflows/rubocop.yml +20 -0
  6. data/.github/workflows/test.yml +163 -0
  7. data/.rubocop.yml +44 -0
  8. data/CHANGELOG.md +28 -1
  9. data/CONTRIBUTING.md +16 -11
  10. data/Gemfile +5 -3
  11. data/README.md +167 -30
  12. data/bug_report_templates/test-ransack-scope-and-column-same-name.rb +78 -0
  13. data/bug_report_templates/test-ransacker-arel-present-predicate.rb +71 -0
  14. data/docs/img/create_release.png +0 -0
  15. data/docs/release_process.md +17 -0
  16. data/{polyamorous/lib → lib}/polyamorous/activerecord_5.2_ruby_2/join_association.rb +4 -0
  17. data/{polyamorous/lib → lib}/polyamorous/activerecord_5.2_ruby_2/join_dependency.rb +0 -0
  18. data/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +11 -0
  19. data/{polyamorous/lib → lib}/polyamorous/activerecord_6.0_ruby_2/join_association.rb +0 -1
  20. data/{polyamorous/lib → lib}/polyamorous/activerecord_6.0_ruby_2/join_dependency.rb +0 -1
  21. data/{polyamorous/lib → lib}/polyamorous/activerecord_6.0_ruby_2/reflection.rb +0 -1
  22. data/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +74 -0
  23. data/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +93 -0
  24. data/{polyamorous/lib → lib}/polyamorous/activerecord_6.1_ruby_2/reflection.rb +0 -1
  25. data/lib/polyamorous/activerecord_7.0_ruby_2/join_association.rb +1 -0
  26. data/lib/polyamorous/activerecord_7.0_ruby_2/join_dependency.rb +1 -0
  27. data/lib/polyamorous/activerecord_7.0_ruby_2/reflection.rb +1 -0
  28. data/{polyamorous/lib → lib}/polyamorous/join.rb +0 -0
  29. data/{polyamorous/lib → lib/polyamorous}/polyamorous.rb +1 -1
  30. data/{polyamorous/lib → lib}/polyamorous/swapping_reflection_class.rb +0 -0
  31. data/{polyamorous/lib → lib}/polyamorous/tree_node.rb +0 -0
  32. data/lib/polyamorous.rb +1 -0
  33. data/lib/ransack/adapters/active_record/base.rb +5 -1
  34. data/lib/ransack/adapters/active_record/context.rb +55 -13
  35. data/lib/ransack/adapters/active_record/ransack/constants.rb +1 -1
  36. data/lib/ransack/adapters/active_record/ransack/context.rb +1 -0
  37. data/lib/ransack/adapters/active_record/ransack/nodes/condition.rb +11 -3
  38. data/lib/ransack/configuration.rb +31 -1
  39. data/lib/ransack/constants.rb +2 -2
  40. data/lib/ransack/helpers/form_builder.rb +3 -3
  41. data/lib/ransack/helpers.rb +1 -1
  42. data/lib/ransack/locale/sv.yml +70 -0
  43. data/lib/ransack/nodes/attribute.rb +1 -1
  44. data/lib/ransack/nodes/condition.rb +0 -2
  45. data/lib/ransack/nodes/grouping.rb +1 -1
  46. data/lib/ransack/nodes/sort.rb +3 -3
  47. data/lib/ransack/nodes/value.rb +1 -1
  48. data/lib/ransack/search.rb +4 -1
  49. data/lib/ransack/translate.rb +4 -4
  50. data/lib/ransack/version.rb +1 -1
  51. data/lib/ransack.rb +2 -2
  52. data/ransack.gemspec +8 -14
  53. data/spec/blueprints/articles.rb +1 -1
  54. data/spec/blueprints/comments.rb +1 -1
  55. data/spec/blueprints/notes.rb +1 -1
  56. data/spec/blueprints/tags.rb +1 -1
  57. data/spec/console.rb +5 -5
  58. data/spec/helpers/ransack_helper.rb +1 -1
  59. data/spec/{ransack → polyamorous}/join_association_spec.rb +8 -1
  60. data/spec/{ransack → polyamorous}/join_dependency_spec.rb +0 -0
  61. data/spec/{ransack → polyamorous}/join_spec.rb +0 -0
  62. data/spec/ransack/adapters/active_record/base_spec.rb +26 -15
  63. data/spec/ransack/adapters/active_record/context_spec.rb +19 -18
  64. data/spec/ransack/configuration_spec.rb +24 -0
  65. data/spec/ransack/helpers/form_helper_spec.rb +16 -16
  66. data/spec/ransack/nodes/condition_spec.rb +13 -0
  67. data/spec/ransack/nodes/grouping_spec.rb +2 -2
  68. data/spec/ransack/predicate_spec.rb +1 -1
  69. data/spec/ransack/search_spec.rb +215 -30
  70. data/spec/spec_helper.rb +7 -5
  71. data/spec/support/schema.rb +28 -2
  72. metadata +45 -47
  73. data/.travis.yml +0 -47
  74. data/polyamorous/lib/polyamorous/activerecord_5.2_ruby_2/reflection.rb +0 -12
  75. data/polyamorous/lib/polyamorous/activerecord_6.1_ruby_2/join_association.rb +0 -2
  76. data/polyamorous/lib/polyamorous/activerecord_6.1_ruby_2/join_dependency.rb +0 -2
  77. data/polyamorous/lib/polyamorous/version.rb +0 -3
  78. data/polyamorous/polyamorous.gemspec +0 -27
@@ -20,6 +20,44 @@ module Ransack
20
20
  Search.new(Person, name_eq: 'foobar')
21
21
  end
22
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
+
23
61
  it 'removes empty suffixed conditions before building' do
24
62
  expect_any_instance_of(Search).to receive(:build).with({})
25
63
  Search.new(Person, name_eq_any: [''])
@@ -109,6 +147,43 @@ module Ransack
109
147
  expect(s.result.to_sql).to include 'published'
110
148
  end
111
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
+ pending("spec should pass, but I do not know how/where to fix lib code")
182
+ s = Search.new(Person, published_articles_title_not_eq: 'Test')
183
+ expect(s.result.to_sql).to include 'default_scope'
184
+ expect(s.result.to_sql).to include 'published'
185
+ end
186
+
112
187
  it 'discards empty conditions' do
113
188
  s = Search.new(Person, children_name_eq: '')
114
189
  condition = s.base[:children_name_eq]
@@ -189,7 +264,7 @@ module Ransack
189
264
  context 'with an invalid condition' do
190
265
  subject { Search.new(Person, unknown_attr_eq: 'Ernie') }
191
266
 
192
- context 'when ignore_unknown_conditions is false' do
267
+ context 'when ignore_unknown_conditions configuration option is false' do
193
268
  before do
194
269
  Ransack.configure { |c| c.ignore_unknown_conditions = false }
195
270
  end
@@ -197,13 +272,39 @@ module Ransack
197
272
  specify { expect { subject }.to raise_error ArgumentError }
198
273
  end
199
274
 
200
- context 'when ignore_unknown_conditions is true' do
275
+ context 'when ignore_unknown_conditions configuration option is true' do
201
276
  before do
202
277
  Ransack.configure { |c| c.ignore_unknown_conditions = true }
203
278
  end
204
279
 
205
280
  specify { expect { subject }.not_to raise_error }
206
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
+ end
304
+
305
+ context 'when ignore_unknown_conditions search parameter is true' do
306
+ specify { expect { with_ignore_unknown_conditions_true }.not_to raise_error }
307
+ end
207
308
  end
208
309
 
209
310
  it 'does not modify the parameters' do
@@ -220,6 +321,9 @@ module Ransack
220
321
  let(:children_people_name_field) {
221
322
  "#{quote_table_name("children_people")}.#{quote_column_name("name")}"
222
323
  }
324
+ let(:notable_type_field) {
325
+ "#{quote_table_name("notes")}.#{quote_column_name("notable_type")}"
326
+ }
223
327
  it 'evaluates conditions contextually' do
224
328
  s = Search.new(Person, children_name_eq: 'Ernie')
225
329
  expect(s.result).to be_an ActiveRecord::Relation
@@ -234,12 +338,14 @@ module Ransack
234
338
  parent_name_eq: "parent_name_query",
235
339
  parent_articles_title_eq: 'parents_article_title_query'
236
340
  }).result
341
+
237
342
  real_query = remove_quotes_and_backticks(s.to_sql)
238
343
 
239
344
  expect(real_query)
240
- .to match(%r{LEFT OUTER JOIN articles ON (\('default_scope' = 'default_scope'\) AND )?articles.person_id = people.id})
345
+ .to match(%r{LEFT OUTER JOIN articles ON (\('default_scope' = 'default_scope'\) AND )?articles.person_id = people.id})
241
346
  expect(real_query)
242
- .to match(%r{LEFT OUTER JOIN articles articles_people ON (\('default_scope' = 'default_scope'\) AND )?articles_people.person_id = parents_people.id})
347
+ .to match(%r{LEFT OUTER JOIN articles articles_people ON (\('default_scope' = 'default_scope'\) AND )?articles_people.person_id = parents_people.id})
348
+
243
349
  expect(real_query)
244
350
  .to include "people.name = 'person_name_query'"
245
351
  expect(real_query)
@@ -282,6 +388,7 @@ module Ransack
282
388
  s = Search.new(Note, notable_of_Person_type_name_eq: 'Ernie').result
283
389
  expect(s).to be_an ActiveRecord::Relation
284
390
  expect(s.to_sql).to match /#{people_name_field} = 'Ernie'/
391
+ expect(s.to_sql).to match /#{notable_type_field} = 'Person'/
285
392
  end
286
393
 
287
394
  it 'evaluates nested conditions' do
@@ -320,11 +427,8 @@ module Ransack
320
427
  { m: 'or', comments_body_cont: 'e', articles_comments_body_cont: 'e' }
321
428
  ]
322
429
  )
323
- if ActiveRecord::VERSION::MAJOR == 3
324
- all_or_load, uniq_or_distinct = :all, :uniq
325
- else
326
- all_or_load, uniq_or_distinct = :load, :distinct
327
- end
430
+
431
+ all_or_load, uniq_or_distinct = :load, :distinct
328
432
  expect(s.result.send(all_or_load).size)
329
433
  .to eq(9000)
330
434
  expect(s.result(distinct: true).size)
@@ -377,88 +481,169 @@ module Ransack
377
481
  expect(sort.dir).to eq 'asc'
378
482
  end
379
483
 
380
- it 'creates sorts based on multiple attributes/directions in array format' do
381
- @s.sorts = ['id desc', { name: 'name', dir: 'asc' }]
484
+ it 'creates sorts based on a single alias/direction' do
485
+ @s.sorts = 'daddy desc'
486
+ expect(@s.sorts.size).to eq(1)
487
+ sort = @s.sorts.first
488
+ expect(sort).to be_a Nodes::Sort
489
+ expect(sort.name).to eq 'parent_name'
490
+ expect(sort.dir).to eq 'desc'
491
+ end
492
+
493
+ it 'creates sorts based on a single alias and uppercase direction' do
494
+ @s.sorts = 'daddy DESC'
495
+ expect(@s.sorts.size).to eq(1)
496
+ sort = @s.sorts.first
497
+ expect(sort).to be_a Nodes::Sort
498
+ expect(sort.name).to eq 'parent_name'
499
+ expect(sort.dir).to eq 'desc'
500
+ end
501
+
502
+ it 'creates sorts based on a single alias and without direction' do
503
+ @s.sorts = 'daddy'
504
+ expect(@s.sorts.size).to eq(1)
505
+ sort = @s.sorts.first
506
+ expect(sort).to be_a Nodes::Sort
507
+ expect(sort.name).to eq 'parent_name'
508
+ expect(sort.dir).to eq 'asc'
509
+ end
510
+
511
+ it 'creates sorts based on attributes, alias and directions in array format' do
512
+ @s.sorts = ['id desc', { name: 'daddy', dir: 'asc' }]
382
513
  expect(@s.sorts.size).to eq(2)
383
514
  sort1, sort2 = @s.sorts
384
515
  expect(sort1).to be_a Nodes::Sort
385
516
  expect(sort1.name).to eq 'id'
386
517
  expect(sort1.dir).to eq 'desc'
387
518
  expect(sort2).to be_a Nodes::Sort
388
- expect(sort2.name).to eq 'name'
519
+ expect(sort2.name).to eq 'parent_name'
389
520
  expect(sort2.dir).to eq 'asc'
390
521
  end
391
522
 
392
- it 'creates sorts based on multiple attributes and uppercase directions in array format' do
393
- @s.sorts = ['id DESC', { name: 'name', dir: 'ASC' }]
523
+ it 'creates sorts based on attributes, alias and uppercase directions in array format' do
524
+ @s.sorts = ['id DESC', { name: 'daddy', dir: 'ASC' }]
394
525
  expect(@s.sorts.size).to eq(2)
395
526
  sort1, sort2 = @s.sorts
396
527
  expect(sort1).to be_a Nodes::Sort
397
528
  expect(sort1.name).to eq 'id'
398
529
  expect(sort1.dir).to eq 'desc'
399
530
  expect(sort2).to be_a Nodes::Sort
400
- expect(sort2.name).to eq 'name'
531
+ expect(sort2.name).to eq 'parent_name'
401
532
  expect(sort2.dir).to eq 'asc'
402
533
  end
403
534
 
404
- it 'creates sorts based on multiple attributes and different directions
535
+ it 'creates sorts based on attributes, alias and different directions
405
536
  in array format' do
406
- @s.sorts = ['id DESC', { name: 'name', dir: nil }]
537
+ @s.sorts = ['id DESC', { name: 'daddy', dir: nil }]
407
538
  expect(@s.sorts.size).to eq(2)
408
539
  sort1, sort2 = @s.sorts
409
540
  expect(sort1).to be_a Nodes::Sort
410
541
  expect(sort1.name).to eq 'id'
411
542
  expect(sort1.dir).to eq 'desc'
412
543
  expect(sort2).to be_a Nodes::Sort
413
- expect(sort2.name).to eq 'name'
544
+ expect(sort2.name).to eq 'parent_name'
414
545
  expect(sort2.dir).to eq 'asc'
415
546
  end
416
547
 
417
- it 'creates sorts based on multiple attributes/directions in hash format' do
548
+ it 'creates sorts based on attributes, alias and directions in hash format' do
418
549
  @s.sorts = {
419
550
  '0' => { name: 'id', dir: 'desc' },
420
- '1' => { name: 'name', dir: 'asc' }
551
+ '1' => { name: 'daddy', dir: 'asc' }
421
552
  }
422
553
  expect(@s.sorts.size).to eq(2)
423
554
  expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
424
555
  id_sort = @s.sorts.detect { |s| s.name == 'id' }
425
- name_sort = @s.sorts.detect { |s| s.name == 'name' }
556
+ daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
426
557
  expect(id_sort.dir).to eq 'desc'
427
- expect(name_sort.dir).to eq 'asc'
558
+ expect(daddy_sort.dir).to eq 'asc'
428
559
  end
429
560
 
430
- it 'creates sorts based on multiple attributes and uppercase directions
561
+ it 'creates sorts based on attributes, alias and uppercase directions
431
562
  in hash format' do
432
563
  @s.sorts = {
433
564
  '0' => { name: 'id', dir: 'DESC' },
434
- '1' => { name: 'name', dir: 'ASC' }
565
+ '1' => { name: 'daddy', dir: 'ASC' }
435
566
  }
436
567
  expect(@s.sorts.size).to eq(2)
437
568
  expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
438
569
  id_sort = @s.sorts.detect { |s| s.name == 'id' }
439
- name_sort = @s.sorts.detect { |s| s.name == 'name' }
570
+ daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
440
571
  expect(id_sort.dir).to eq 'desc'
441
- expect(name_sort.dir).to eq 'asc'
572
+ expect(daddy_sort.dir).to eq 'asc'
442
573
  end
443
574
 
444
- it 'creates sorts based on multiple attributes and different directions
575
+ it 'creates sorts based on attributes, alias and different directions
445
576
  in hash format' do
446
577
  @s.sorts = {
447
578
  '0' => { name: 'id', dir: 'DESC' },
448
- '1' => { name: 'name', dir: nil }
579
+ '1' => { name: 'daddy', dir: nil }
449
580
  }
450
581
  expect(@s.sorts.size).to eq(2)
451
582
  expect(@s.sorts).to be_all { |s| Nodes::Sort === s }
452
583
  id_sort = @s.sorts.detect { |s| s.name == 'id' }
453
- name_sort = @s.sorts.detect { |s| s.name == 'name' }
584
+ daddy_sort = @s.sorts.detect { |s| s.name == 'parent_name' }
454
585
  expect(id_sort.dir).to eq 'desc'
455
- expect(name_sort.dir).to eq 'asc'
586
+ expect(daddy_sort.dir).to eq 'asc'
456
587
  end
457
588
 
458
589
  it 'overrides existing sort' do
459
590
  @s.sorts = 'id asc'
460
591
  expect(@s.result.first.id).to eq 1
461
592
  end
593
+
594
+ it "PG's sort option", if: ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" do
595
+ default = Ransack.options.clone
596
+
597
+ s = Search.new(Person, s: 'name asc')
598
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC"
599
+
600
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_first }
601
+ s = Search.new(Person, s: 'name asc')
602
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC NULLS FIRST"
603
+ s = Search.new(Person, s: 'name desc')
604
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" DESC NULLS LAST"
605
+
606
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_last }
607
+ s = Search.new(Person, s: 'name asc')
608
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" ASC NULLS LAST"
609
+ s = Search.new(Person, s: 'name desc')
610
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" DESC NULLS FIRST"
611
+
612
+ Ransack.options = default
613
+ end
614
+
615
+ it "PG's sort option with double name", if: ::ActiveRecord::Base.connection.adapter_name == "PostgreSQL" do
616
+ default = Ransack.options.clone
617
+
618
+ s = Search.new(Person, s: 'doubled_name asc')
619
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC"
620
+
621
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_first }
622
+ s = Search.new(Person, s: 'doubled_name asc')
623
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS FIRST"
624
+ s = Search.new(Person, s: 'doubled_name desc')
625
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS LAST"
626
+
627
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_last }
628
+ s = Search.new(Person, s: 'doubled_name asc')
629
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS LAST"
630
+ s = Search.new(Person, s: 'doubled_name desc')
631
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS FIRST"
632
+
633
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_always_first }
634
+ s = Search.new(Person, s: 'doubled_name asc')
635
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS FIRST"
636
+ s = Search.new(Person, s: 'doubled_name desc')
637
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS FIRST"
638
+
639
+ Ransack.configure { |c| c.postgres_fields_sort_option = :nulls_always_last }
640
+ s = Search.new(Person, s: 'doubled_name asc')
641
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" ASC NULLS LAST"
642
+ s = Search.new(Person, s: 'doubled_name desc')
643
+ expect(s.result.to_sql).to eq "SELECT \"people\".* FROM \"people\" ORDER BY \"people\".\"name\" || \"people\".\"name\" DESC NULLS LAST"
644
+
645
+ Ransack.options = default
646
+ end
462
647
  end
463
648
 
464
649
  describe '#method_missing' do
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'machinist/active_record'
2
+ require 'polyamorous/polyamorous'
2
3
  require 'sham'
3
4
  require 'faker'
4
5
  require 'ransack'
@@ -16,16 +17,17 @@ I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'support', '*.yml')]
16
17
  Dir[File.expand_path('../{helpers,support,blueprints}/*.rb', __FILE__)]
17
18
  .each { |f| require f }
18
19
 
20
+ Faker::Config.random = Random.new(0)
19
21
  Sham.define do
20
22
  name { Faker::Name.name }
21
23
  title { Faker::Lorem.sentence }
22
24
  body { Faker::Lorem.paragraph }
23
25
  salary { |index| 30000 + (index * 1000) }
24
- tag_name { Faker::Lorem.words(3).join(' ') }
25
- note { Faker::Lorem.words(7).join(' ') }
26
- only_admin { Faker::Lorem.words(3).join(' ') }
27
- only_search { Faker::Lorem.words(3).join(' ') }
28
- only_sort { Faker::Lorem.words(3).join(' ') }
26
+ tag_name { Faker::Lorem.words(number: 3).join(' ') }
27
+ note { Faker::Lorem.words(number: 7).join(' ') }
28
+ only_admin { Faker::Lorem.words(number: 3).join(' ') }
29
+ only_search { Faker::Lorem.words(number: 3).join(' ') }
30
+ only_sort { Faker::Lorem.words(number: 3).join(' ') }
29
31
  notable_id { |id| id }
30
32
  end
31
33
 
@@ -6,6 +6,8 @@ when 'mysql', 'mysql2'
6
6
  ActiveRecord::Base.establish_connection(
7
7
  adapter: 'mysql2',
8
8
  database: 'ransack',
9
+ username: ENV.fetch("MYSQL_USERNAME") { "root" },
10
+ password: ENV.fetch("MYSQL_PASSWORD") { "" },
9
11
  encoding: 'utf8'
10
12
  )
11
13
  when 'pg', 'postgres', 'postgresql'
@@ -85,7 +87,6 @@ class Person < ActiveRecord::Base
85
87
  )
86
88
  end
87
89
 
88
-
89
90
  ransacker :sql_literal_id do
90
91
  Arel.sql('people.id')
91
92
  end
@@ -108,7 +109,6 @@ class Person < ActiveRecord::Base
108
109
  Arel.sql(query)
109
110
  end
110
111
 
111
-
112
112
  def self.ransackable_attributes(auth_object = nil)
113
113
  if auth_object == :admin
114
114
  super - ['only_sort']
@@ -138,6 +138,29 @@ class Article < ActiveRecord::Base
138
138
  alias_attribute :content, :body
139
139
 
140
140
  default_scope { where("'default_scope' = 'default_scope'") }
141
+
142
+ ransacker :title_type, formatter: lambda { |tuples|
143
+ title, type = JSON.parse(tuples)
144
+ Arel::Nodes::Grouping.new(
145
+ [
146
+ Arel::Nodes.build_quoted(title),
147
+ Arel::Nodes.build_quoted(type)
148
+ ]
149
+ )
150
+ } do |_parent|
151
+ articles = Article.arel_table
152
+ Arel::Nodes::Grouping.new(
153
+ %i[title type].map do |field|
154
+ Arel::Nodes::NamedFunction.new(
155
+ 'COALESCE',
156
+ [
157
+ Arel::Nodes::NamedFunction.new('TRIM', [articles[field]]),
158
+ Arel::Nodes.build_quoted('')
159
+ ]
160
+ )
161
+ end
162
+ )
163
+ end
141
164
  end
142
165
 
143
166
  class StoryArticle < Article
@@ -164,6 +187,8 @@ end
164
187
  class Comment < ActiveRecord::Base
165
188
  belongs_to :article
166
189
  belongs_to :person
190
+
191
+ default_scope { where(disabled: false) }
167
192
  end
168
193
 
169
194
  class Tag < ActiveRecord::Base
@@ -209,6 +234,7 @@ module Schema
209
234
  t.integer :article_id
210
235
  t.integer :person_id
211
236
  t.text :body
237
+ t.boolean :disabled, default: false
212
238
  end
213
239
 
214
240
  create_table :tags, force: true do |t|